面試驅動技術之 - 帶著面試題來找答案
- 一個NSObject 物件,佔用多少記憶體
- 物件方法 與 類方法的存放在哪
- 什麼是isa指標
- 什麼是meta-class
- megsend 是如何找到方法的
@implementation MNSubclass
- (void)compareSelfWithSuperclass{
NSLog(@"self class = %@",[self class]);
NSLog(@"super class = %@",[super class]);
}
@end
複製程式碼
- 輸出的結果是什麼
- 。。。
友情tips:如果上訴問題你都知道答案,或者沒有興趣知道,就可以不用繼續往下看了,興趣是最好的老師,如果沒有興趣知道這些,往下很難讀得進去~
OC物件的本質
我們平時編寫的Objetcive-C,底層實現都是C/C++實現的
- 問 : Objetcive-C 基於 C/C++ 實現的話,Objetcive-C 物件相當於C/C++ 中的什麼資料結構呢?
@interface MNPerson : NSObject
{
int _age;
double _height;
NSString *name;
}
複製程式碼
以MNPerson
為例,裡面的成員變數有不同型別是,比如int
、double
、NSString
型別,假如在C/C++ 中用陣列
儲存,顯然是不太合理的
- 答: C/C++中用
結構體
的資料格式,表示oc物件。
// 轉成c/c++ 程式碼後,MNPerson 的結構如下
struct NSObject_IMPL {
Class isa;
};
struct MNPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
double _height;
NSString *name;
};
複製程式碼
使用指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc oc原始檔 -o 輸出的c++檔案
將oc
程式碼轉成c++
程式碼之後,發現內部確實是結構體
面試題來襲!前方請做好準備!!
-
一個NSObject 物件,佔用多少記憶體
-
思路:
-
- 由上面可知,
NSObject
的本質是結構體,通過NSObject.m
可以發現,NSObject
只有一個isa
成員,isa
的本質是class
,struct objc_object *
型別,所以應該佔據 8 位元組
- 由上面可知,
- 2.
NSLog(@"%zu",class_getInstanceSize([NSObject class]));
輸出 - size = 8
-
-
注意!實際上,
{
//獲得 - NSObject 一個例項物件的成員變數所佔用的大小 >> 8
NSLog(@"%zu",class_getInstanceSize([NSObject class]));
NSObject *obj = [[NSObject alloc]init];
// 獲取 obj 指標,指向的記憶體大小 >> 16
NSLog(@"%zu",malloc_size((__bridge const void *)obj));
}
複製程式碼
//基於 `objc-class.m` 檔案 750 版本
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class‘s ivar size rounded up to a pointer-size boundary.
// 點選一下 - 智慧翻譯 ==> (返回類的成員變數所佔據的大小)
uint32_t alignedInstanceSize()
{
return word_align(unalignedInstanceSize());
}
複製程式碼
物件建立 - alloc init
, 查詢alloc底層實現
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
複製程式碼
CoreFoundation
硬性規定,一個物件,至少有 16 位元組- 以
NSObject
為例,這裡傳入的size
是alignedInstanceSize
, 而alignedInstanceSize
已經知道 = 8, 8 < 16,retun 16, 最終 NSObject 建立的物件,佔據的記憶體大小是 16
lldb 除錯下,使用 memory read
檢視物件記憶體
(lldb) p obj
(NSObject *) $0 = 0x000060000000eb90
(lldb) memory read 0x000060000000eb90
0x60000000eb90: a8 6e 3a 0b 01 00 00 00 00 00 00 00 00 00 00 00
複製程式碼
也能發現,前8 位儲存 isa
指標,後 8 位都是0,但是整個物件還是佔據了 16 個位元組
一個NSObject記憶體分配示意圖
總結:
- 問 :一個NSObject 物件,佔用多少記憶體?
- 答 :
- 系統在alloc的時候,分配了16個位元組給
NSObject
物件(malloc_size
函式獲得) - 但是實際上
NSObject
只使用了 8個位元組的儲存空間(64bit系統下) - 可以通過
class_getInstanceSize()
- 系統在alloc的時候,分配了16個位元組給
循序漸進之面試題又來了!!
@interface MNStudent : NSObject
{
int _age;
int _no;
}
@end
複製程式碼
- 問:一個MNStudent 物件,佔用多少記憶體
- 答:
- 由上面
NSObject
佔據16個位元組可知,base = 16 - 一個int佔4位元組,age = 4, no = 4
- 最終結果, 16 + 4 + 4 = 24!
- 由上面
哈哈!中計了!
原理解釋:
- 之前
NSObject
建立一個物件,確實是分配了 16 個位元組的空間- 但是,他還有未使用的空間8個位元組,還是可以儲存的
- 這裡的
age
&&no
存進去,正好16
,之前分配的空間正好夠用!所以答案是 16!
循循序漸進之面試題雙來了!!
(大哥別打了,這次保證不挖坑了,別打,疼,別打臉別打臉。。。)
@interface MNPerson : NSObject
{
int _age;
int _height;
NSString *name;
}
複製程式碼
- 問: 一個
MNPerson
物件,佔用多少記憶體 - 答: 這題我真的會了!上面的坑老夫已經知道了!
- 預設建立的時候,分配的內容是16
isa
= 8,int age
= 4,int height
= 4,NSString
= char * = 8- 最終分配: 8 + 4 + 4 + 8 = 24
哈哈哈哈! 又中計了!
這時候你肯定好奇了
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
複製程式碼
extraBytes
一般都是 0,這裡可以理解為size = alignedInstanceSize()
;alignedInstanceSize = class_getInstanceSize
,class_getInstanceSize
由上圖的log資訊也可以知道 =24
- 內心os: who tm fucking 32?
下載`libmalloc`,找到`calloc`的實現
void *
calloc(size_t num_items, size_t size)
{
void *retval;
retval = malloc_zone_calloc(default_zone, num_items, size);
if (retval == NULL) {
errno = ENOMEM;
}
return retval;
}
發現這個函式,就是我們呼叫的`calloc`函式的底層
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
void *ptr;
if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
internal_check();
}
ptr = zone->calloc(zone, num_items, size);
if (malloc_logger) {
malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
}
MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
return ptr;
}
複製程式碼
內心os: exo me? 這傳入的 size_t size = 24
,怎麼返回32的??
涉及到 - 記憶體對齊
檢索Buckets
- (libmalloc 原始碼)
#define NANO_MAX_SIZE 256
/* Buckets sized {16, 32, 48, ..., 256} */
複製程式碼
- 發現,iOS 系統分配的時候,有自己的分配規則, 不是說你需要的size多大,就建立多大
- 作業系統內部有自己的一套規則,這裡的都是 16 的倍數,而作業系統在此基礎之上,作業系統的操作訪問最快
- 所以,在
MNPerson
物件需要 24 的size的時候,作業系統根據他的規則,直接建立了 32 的size的空間,所以這裡的答案是 32!
補充說明: sizeof
運算子
(lldb) po [obj class]
MNPerson
(lldb) po sizeof(obj)
8
(lldb) po sizeof(int)
4
複製程式碼
sizeof
是運算子,不是函式,編譯時即知道,不是函式- 這裡的
obj
是物件, *obj - 指標指向,編譯器知道他是指標型別,所以 sizeof = 8(指標佔據8個位元組) - 特別注意,這裡傳入的不是物件!是指標sizeof
是告訴你傳入的型別,佔多少儲存空間
OC物件的分類
- 例項物件(instance物件)
- 類物件(class物件)
- 元類物件(meta-class物件)
instance 物件
- 通過類
alloc
出來的物件 - 每次
alloc
都會產生新的instance
物件(記憶體不相同) instance
物件儲存的資訊- isa 指標
- 其他成員變數
class 物件
- 是建立物件的藍圖,描述了所建立的物件共同的屬性和方法(made in 維基百科)
- 類在記憶體中只有一份,每個類在記憶體中都有且只有一個
class
物件 class
物件在記憶體中儲存的資訊- isa 指標
- superclass 指標
- 類的物件方法 && 協議
- 類的屬性 && 成員變數資訊
- 。。。
meta-class
Class metaClass = object_getClass([NSObject class]);
metaclss
是NSObject
的meta-class
物件meta-class
在記憶體中只有一份,每個類都有且只有一個meta-class
物件meta-class
也是類,與class
的物件結構一樣,但是內部的資料不一樣(用途不同)meta-clas
包括:- isa指標
- superclass
- 類方法
- 。。。
提問:object_getClass
與 objc_getClass
的區別
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
複製程式碼
- object_getClass : 傳入的是可以是任意物件(id型別),返回的是類 or 元類
- 如果傳入
instance
物件,返回class
- 如果傳入
class
, 返回的是meta-class
物件 - 如果傳入的是
meta-class
,返回的是root-meta-class
物件
- 如果傳入
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
複製程式碼
- 傳入的是類名字串,返回的是該類名對應的類
- 不能返回元類
(圖片來自於 www.sealiesoftware.com/blog/archiv…)
看懂這張圖 - 就等價於看懂
isa
&&superclass
了
探究流程:
@interface MNSuperclass : NSObject
- (void)superclassInstanceMethod;
+ (void)superClassMethod;
@end
@implementation MNSuperclass
- (void)superclassInstanceMethod{
NSLog(@"superclass-InstanceMethod - %p",self);
}
+ (void)superClassMethod{
NSLog(@"+ superClass-classMethod- %p",self);
}
@end
@interface MNSubclass : MNSuperclass
- (void)subclassInstanceMethod;
@end
@implementation MNSubclass
- (void)subclassInstanceMethod{
NSLog(@"subclassInstanceMethod- %p",self);
}
@end
複製程式碼
問: 子類呼叫父類的物件方法,執行的流程是如何的?
MNSubclass *subclass = [[MNSubclass alloc]init];
[subclass superclassInstanceMethod];
複製程式碼
- 思路:
subclass
呼叫物件方法,物件方法存在class
中- 第一步,先找到
subclass
物件,通過isa
指標,找到其對應的MNSubclass
類 - 看
MNSubclass
是否有superclassInstanceMethod
方法的實現,發現沒有,MNSubclass
沿著superclass
指標找到他的父類 -MNSuperclass
- 此時,
MNSuperclass
中找到superclassInstanceMethod
的實現,呼叫它,整個流程結束
問: 子類呼叫父類的類方法,執行的流程是如何的?
[MNSubclass superClassMethod];
- 思路:
- 類方法存在
meta-class
中 - 第一步,找到對應的
MNSubclass
,沿著isa
指標,找到其對應的meta-class
- 看
MNSubclass
的meta-class
中是否有superClassMethod
方法的實現,發現沒有,沿著superclass
指標找到MNSuperclass
的meta-class
- 發現
MNSuperclass
的meta-class
有superClassMethod
方法實現,呼叫,流程結束
- 類方法存在
圖中比較難理解的一根線
探究 : 元類物件的superclass 指標是否指向 rootclass
- 分析:
meta-class
物件儲存的是類方法,class
儲存的是 物件方法- 從物件導向的角度來講,一個類呼叫一個類方法,不應該最後呼叫到 物件方法
- 這裡的
Root class
就是NSObject
, 要給NSObject
新增方法就要用到分類
- 驗證
NSObject
的物件方法是否會被呼叫
//"NSObject+MNTest"類的宣告 && 實現
@interface NSObject (MNTest)
+ (void)checkSuperclass;
@end
@implementation NSObject (MNTest)
+ (void)checkSuperclass{
NSLog(@"+NSObject checkSuperclass - %p",self);
}
@end
//main函式中呼叫
int main(int argc, char * argv[]) {
@autoreleasepool
{
[MNSubclass checkSuperclass];
NSLog(@"MNSubclass = %p",[MNSubclass class]);
}
return 0;
}
--------------------------------------------------------
控制檯輸出:
+NSObject checkSuperclass - 0x105817040
InterView-obj-isa-class[36303:7016608] MNSubclass = 0x105817040
複製程式碼
- 發現,呼叫
checkSuperclass
類方法的,是MNSubclass
類- 這時候要驗證上面那條
meta-class
指向root-class
的線, 這裡的root-class
即等於NSObject
root-class
中只存物件方法,這裡,只要驗證,NSObject
中同名的類方法實現取消,變成同名的物件方法測試,即可得出結論- 宣告的還是
+ (void)checkSuperclass
,實現的方法用- (void)checkSuperclass
物件方法替換
@interface NSObject (MNTest)
+ (void)checkSuperclass;
@end
@implementation NSObject (MNTest)
//+ (void)checkSuperclass{
// NSLog(@"+NSObject checkSuperclass - %p",self);
//}
- (void)checkSuperclass{
NSLog(@"-NSObject checkSuperclass - %p",self);
}
@end
//main函式中呼叫
int main(int argc, char * argv[]) {
@autoreleasepool
{
[MNSubclass checkSuperclass];
NSLog(@"MNSubclass = %p",[MNSubclass class]);
}
return 0;
}
--------------------------------------------------------
控制檯輸出:
-NSObject checkSuperclass - 0x101239040
InterView-obj-isa-class[36391:7022301] MNSubclass = 0x101239040
複製程式碼
發現 - 呼叫的還是類方法 + (void)checkSuperclass
,但是最終實現的,卻是物件方法 - (void)checkSuperclass
- 原因:
[MNSubclass checkSuperclass]
其實本質上,呼叫的是傳送訊息方法,函式類似是objc_msgsend([MNSubclass class], @selector(checkSuperclass))
- 這裡的
@selector(checkSuperclass)
並未說明是 類方法 or 物件方法 - 所以最終走流程圖的話,
root-meta-class
通過super class
找到了root-class
(NSObject), NSObject
類不是元類,儲存的是物件方法,所以 最終呼叫了NSObject -checkSuperclass
這個物件方法
叮叮叮!循循循序漸進之面試題叒來了!!
@implementation MNSubclass
- (void)compareSelfWithSuperclass{
NSLog(@"self class = %@",[self class]);
NSLog(@"super class = %@",[super class]);
}
@end
----------------------------------------
呼叫:
MNSubclass *subclass = [[MNSubclass alloc]init];
[subclass compareSelfWithSuperclass];
複製程式碼
-
問:
[self class]
&&[super class]
分別輸出什麼 -
思路:
class
方法 是NSObject
的一個物件方法,對方方法存在class
中- 第一步,先找到
subclass
物件,通過isa
指標,找到其對應的MNSubclass
類 - 看
MNSubclass
是否有class
方法的實現,發現沒有,MNSubclass
沿著superclass
指標找到他的父類 -MNSuperclass
- 查詢
MNSuperclass
中是否有class
方法的實現,發現沒有,MNSuperclass
沿著superclass
指標找到他的父類 -NSObject
- 最終在
NSObject
中找到class
的實現 - 而呼叫方都是都是當前物件,所以最後輸出都是 -
MNSubclass
驗證:
NSLog(@"self class = %@",[self class]);
NSLog(@"super class = %@",[super class]);
----------------------------------------------------------------------
控制檯輸出:
InterView-obj-isa-class[36796:7048007] self class = MNSubclass
InterView-obj-isa-class[36796:7048007] super class = MNSubclass
複製程式碼
感謝小碼哥 的精彩演出,文章中如果有說錯的地方,歡迎提出,一起學習~
歡迎點贊fork~
友情客串: