本文的面試題主要涉及isa走位 & 繼承關(guān)系 & 類結(jié)構(gòu)
相關(guān)的面試題
以及針對面試題的分析
【面試題】類存在幾份?
由于類的信息
在內(nèi)存
中永遠只存在一份
,所以 類對象只有一份
【百度面試題】objc_object 與 對象的關(guān)系
所有的
對象
都是以objc_object
為模板繼承
過來的所有的對象 是 來自
NSObject
(OC) 遮精,但是真正到底層的 是一個objc_object(C/C++)
的結(jié)構(gòu)體類型
【總結(jié)】 objc_object
與 對象
的關(guān)系
是 繼承
關(guān)系
【面試題】什么是 屬性 & 成員變量 & 實例變量 核芽?
屬性
(property):在OC中是通過@property開頭定義
合陵,且是帶下劃線成員變量
+setter
+getter
方法的變量成員變量
(ivar):在OC的類中{}中定義
的潭兽,且沒有下劃線
的變量實例變量
:通過當(dāng)前對象類型,具備實例化的變量
王浴,是一種特殊的成員變量
脆炎,例如 NSObject、UILabel氓辣、UIButton等
【附加】成員變量 和 實例變量什么區(qū)別秒裕?
實例變量
(即成員變量
中的對象變量
就是實例變量
):以實例對象實例化來的,是一種特殊的成員變量
NSString
是常量
類型钞啸, 因為不能添加屬性
几蜻,如果定義在類中的{}
中,是成員變量
成員變量
中 除去基本數(shù)據(jù)類型体斩、NSString
梭稚,其他都是實例變量
(即可以添加屬性
的成員變量
),實例變量主要是判斷是不是對象
【面試題】元類 中為什么會有 類對象 的 類方法絮吵?
在中的探索中弧烤,我們知道了實例方法
存儲在類
中,類方法
存儲在元類
中
為了探索我們的面試題現(xiàn)象源武,定義了以下幾個方法扼褪,來探索方法
的歸屬問題
- 在LGPerson中定義一個實例方法和一個類方法
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
@end
- main 主函數(shù),用于調(diào)用自定義的方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
- lgObjc_copyMethodList 函數(shù):用于獲取類的方法列表
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//獲取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
- lgInstanceMethod_classToMetaclass 函數(shù):用于獲取類的實例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
- lgClassMethod_classToMetaclass 函數(shù):用于獲取類的類方法
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元類 為什么有 sayHappy 類方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
- lgIMP_classToMetaclass 函數(shù):用于獲取方法的實現(xiàn)
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
以下是幾個函數(shù)調(diào)用的打印結(jié)果
下面我們挨個分析不同的函數(shù)
lgObjc_copyMethodList函數(shù) 分析
在這個函數(shù)中粱栖,主要是獲取LGPerson類中的方法列表,從實例方法存儲在類中脏毯,類方法存儲在元類中
可以得知闹究,LGPerson的方法列表打印結(jié)果只有sayHello
方法
lgInstanceMethod_classToMetaclass函數(shù) 分析
在分析前,需要先了解class_getInstanceMethod
這個方法食店,主要是用于獲取實例方法渣淤,針對該方法赏寇,蘋果有如下說明
其大致含義就是:如果在傳入的類或者類的父類中沒有找到指定的實例方法,則返回NULL
從上面代碼可知价认,傳入的pclass
是類LGPerson
梆砸,通過objc_getMetaClass
獲取的LGPerson的元類 是元類LGPerson
拿霉,函數(shù)中4個打印結(jié)果分別是:
-
method1
地址:0x1000031b0
- 傳入的
pClass
是LGPerson類
,需要去獲取selName = sayHello
的實例方法 - 首先在
LGPerson
中查找,有前面的LGPerson
類可知盈魁,是有這個實例方法的,所以會返回查找到的實例方法惋鹅,所以method1
的地址不為0x0
- 傳入的
-
method2
地址:0x0
- 傳入的
pClass
是LGPerson元類
犁河,需要去獲取selName = sayHello
的實例方法 - 其查找的順序為
元類 --> 根元類 --> 根類 --> nil
,直到最后也沒有找到,所以class_getInstanceMethod
返回NULL
惠奸,其method2
的地址為0x0
梅誓,表示未找到
- 傳入的
-
method3
地址:0x0
- 傳入的
pClass
是LGPerson類
,需要去獲取selName = sayHappy
的實例方法 - 查找順序為
LGPerson類 --> 根類 --> nil
,也沒有找到sayhello
實例方法佛南,返回NULL
梗掰,所以method3
的地址為0x0
,表示未找到
- 傳入的
-
method4
地址:0x100003148
- 傳入的
pClass
是LGPerson元類
嗅回,需要去獲取selName = sayHappy
的實例方法 - 首先在
LGPerson元類
中查找,發(fā)現(xiàn)有sayHappy
的實例方法愧怜,主要是因為類對象的類方法存儲在元類
中,類方法在元類中是實例方法
妈拌,然后返回查找到的實例方法拥坛,所以method3
的地址為0x100003148
,表示找到了指定的實例方法
- 傳入的
lgClassMethod_classToMetaclass函數(shù) 分析
在分析前尘分,需要先了解class_getClassMethod
這個方法猜惋,主要是用于獲取類方法,針對該方法培愁,蘋果有如下說明
其大致含義就是:如果在傳入的類或者類的父類中沒有找到指定的類方法著摔,則返回NULL
然后再來看該方法的源碼實現(xiàn),可以得出class_getClassMethod
的實現(xiàn)是獲取類的類方法
定续,其本質(zhì)
就是獲取元類的實例方法
谍咆,最終還是會走到class_getInstanceMethod
,但是在這里需要注意的一點是:在getMeta
源碼中私股,如果判斷出cls是元類
摹察,那么就不會再繼續(xù)往下遞歸查找,會直接返回this
倡鲸,其目的是為了防止元類
的無限遞歸查找
//獲取類方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
??
//獲取元類
// NOT identical to this->ISA when this is a metaclass 判斷是否是元類供嚎,是元類就直接返回,反之,繼續(xù)找isa指向
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
源碼流程圖如下所示
所以針對該函數(shù)的打印結(jié)果有以下分析
-
method1 地址:0x0
-
pClass
是LGPerson類
克滴,selName
是sayHello
- 首先判斷
LGPerson類
是否是元類
逼争,此時不是
,返回LGPerson的元類
劝赔,然后在元類
中查找sayhello
實例方法誓焦。查找順序如下:元類 --> 根元類 --> 根類 --> nil
,最后返回NULL
-
-
method2 地址:0x0
-
pClass
是LGPerson元類
着帽,selName
是sayHello
- 首先判斷
LGPerson元類
是否是元類
杂伟,此時是
,直接返回元類启摄,然后在元類
中查找sayhello
實例方法稿壁,發(fā)現(xiàn)并沒有找到,返回NULL
-
-
method3 地址:0x100003148
-
pClass
是LGPerson類
歉备,selName
是sayHappy
- 首先判斷
LGPerson類
是否是元類
傅是,此時不是
,返回LGPerson的元類
蕾羊,然后在元類
中查找sayHappy
實例方法喧笔,發(fā)現(xiàn)有這個實例方法,直接返回找到的實例方法
-
-
method4 地址:0x100003148
-
pClass
是LGPerson元類
龟再,selName
是sayHappy
- 首先判斷
LGPerson元類
是否是元類
书闸,此時是
,直接返回元類利凑,然后在元類
中查找sayHappy
實例方法浆劲,發(fā)現(xiàn)有這個實例方法,直接返回找到的實例方法
-
從上面的分析結(jié)果中哀澈,我們就發(fā)現(xiàn)了一個問題 method4
也不為NULL
牌借,此時就很疑惑:元類中為什么會有 sayHappy 類方法?
主要還是因為class_getClassMethod
方法在元類的判斷
導(dǎo)致的割按,這是蘋果人為制造的 遞歸終止條件
膨报,目的就是防止無限次遞歸
lgIMP_classToMetaclass函數(shù) 分析
class_getMethodImplementation
主要是返回方法的具體實現(xiàn)
,針對這個方法有如下官方說明
其大致含義就是:該函數(shù)在向類實例發(fā)送消息時會被調(diào)用适荣,并返回一個指向方法實現(xiàn)函數(shù)的指針
现柠。這個函數(shù)會比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函數(shù)指針
可能是一個指向runtime內(nèi)部的函數(shù)
弛矛,而不一定是方法的實際實現(xiàn)
够吩。如果類實例無法響應(yīng)selector,則返回的函數(shù)指針
將是運行時消息轉(zhuǎn)發(fā)機制
的一部分
下面我們也可以通過這個方法的源碼來印證上面的這個說法汪诉,
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法實現(xiàn)
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
//如果沒有找到废恋,則進行消息轉(zhuǎn)發(fā)
if (!imp) {
return _objc_msgForward;
}
return imp;
}
接下來分析這個函數(shù)中的4個打印結(jié)果
- imp1 函數(shù)指針地址:0x100001d00
-
pClass
是LGPerson類
谈秫,sel
是sayHello
- 根據(jù)
LGPerson
文件扒寄,可以得出LGPerson類中可以查找到sayHello
的具體實現(xiàn)鱼鼓,所以返回一個imp函數(shù)指針的地址
-
-
imp2 函數(shù)指針地址:0x7fff66238d80
-
pClass
是LGPerson元類
,sel
是sayHello
- 根據(jù)
類方法存儲在元類中
可知该编,sayHello是一個實例方法迄本,并不存儲在元類中,也沒有其任何實現(xiàn)课竣,所以進行了消息轉(zhuǎn)發(fā)
-
-
imp3 函數(shù)指針地址:0x7fff66238d80
-
pClass
是LGPerson類
嘉赎,sel
是sayHappy
- 根據(jù)
LGPerson
文件,sayHappy
是一個類方法于樟,并不存儲在類中公条,也沒有其任何實現(xiàn),所以進行了消息轉(zhuǎn)發(fā)
-
-
imp4 函數(shù)指針地址:0x100001d30
-
pClass
是LGPerson元類
迂曲,sel
是sayHappy
- 根據(jù)
類方法存儲在元類
文件靶橱,可以在元類中查找到sayHappy
的具體實現(xiàn),所以返回一個imp函數(shù)指針的地址
-
總結(jié)
class_getInstanceMethod
:獲取實例方法
路捧,如果指定的類
或其父類不包含
帶有指定選擇器的實例方法
关霸,則為NULL
class_getClassMethod
:獲取類方法
,如果指定的類
或其父類不包含
具有指定選擇器的類方法
杰扫,則為NULL
队寇。class_getMethodImplementation
:獲取方法
的具體實現(xiàn)
,如果未查找到
章姓,則進行消息轉(zhuǎn)發(fā)
【面試題】iskindOfClass & isMemberOfClass 的理解
有這么幾行關(guān)于iskindOfClass & isMemberOfClass
的代碼佳遣,分析出最終結(jié)果
- iskindOfClass & isMemberOfClass 類方法調(diào)用
//-----使用 iskindOfClass & isMemberOfClass 類方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
- iskindOfClass & isMemberOfClass 實例方法調(diào)用
//------iskindOfClass & isMemberOfClass 實例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
其最終結(jié)果打印如下
源碼解析
先分析結(jié)果之前,首先需要分析下這兩個方法的源碼
- isKindOfClass 源碼解析(實例方法 & 類方法)
//--isKindOfClass---類方法凡伊、對象方法
//+ isKindOfClass:第一次比較是 獲取類的元類 與 傳入類對比零渐,再次之后的對比是獲取上次結(jié)果的父類 與 傳入 類進行對比
+ (BOOL)isKindOfClass:(Class)cls {
// 獲取類的元類 vs 傳入類
// 根元類 vs 傳入類
// 根類 vs 傳入類
// 舉例:LGPerson vs 元類 (根元類) (NSObject)
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//- isKindOfClass:第一次是獲取對象類 與 傳入類對比窗声,如果不相等相恃,后續(xù)對比是繼續(xù)獲取上次 類的父類 與傳入類進行對比
- (BOOL)isKindOfClass:(Class)cls {
/*
獲取對象的類 vs 傳入的類
父類 vs 傳入的類
根類 vs 傳入的類
nil vs 傳入的類
*/
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- isMemberOfClass 源碼解析(實例方法 & 類方法)
//-----類方法
//+ isMemberOfClass : 獲取類的元類,與 傳入類對比
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//-----實例方法
//- isMemberOfClass : 獲取對象的類笨觅,與 傳入類對比
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
源碼分析總結(jié)
-
isKindOfClass
- 類方法:
元類(isa) --> 根元類(父類) --> 根類(父類) --> nil(父類)
與傳入類
的對比 - 實例方法:
對象的類 --> 父類 --> 根類 --> nil
與傳入類
的對比
- 類方法:
-
isMemberOfClass
- 類方法:
類的元類
與傳入類
對比 - 實例方法:
對象的父類
與傳入類
對比
- 類方法:
然后通過斷點
調(diào)試拦耐,isMemberOfClass
的類方法
和 實例方法
的流程是正常
的,會走到上面分析的源碼见剩,而isKindOfClass
根本不會走到上面分析的源碼
中(I迸础!苍苞!注意這里固翰,這是一個坑點
)狼纬,而是會走到下面這個源碼中,其類方法和實例方法都是走到objc_opt_isKindOfClass
方法源碼中
-
匯編調(diào)用如下
匯編查看 objc_opt_isKindOfClass
方法源碼如下
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
//獲取isa骂际,
//如果obj 是對象疗琉,則isa是類,
//如果obj是類歉铝,則isa是元類
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
// 如果obj 是對象盈简,則在類的繼承鏈進行對比,
// 如果obj是類太示,則在元類的isa中進行對比
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
為什么會這樣呢柠贤?主要是因為在llvm
中編譯時對其進行了優(yōu)化處理
所以調(diào)用objc_opt_isKindOfClass
實際走的邏輯如圖所示
案例代碼執(zhí)行結(jié)果分析
根據(jù)源碼的分析,來分析代碼執(zhí)行的結(jié)果為什么是0
或者1
使用類方法結(jié)果解析
- re1 :1 类缤,是
NSObject
與NSObject
的對比臼勉,使用+isKindOfClass
- NSObject(傳入類,即
根類
) vs NSObject的元類即根元類
-- 不相等 - NSObject(傳入類餐弱,即
根類
) vs 根元類的父類即根類
-- 相等宴霸,返回1
- NSObject(傳入類,即
- re2 :0 ,是
NSObject
與NSObject
的對比岸裙,使用+isMemberOfClass
- NSObject
根類
(傳入類) vs NSObject的元類即根元類
-- 不相等
- NSObject
- re3 :0 猖败,是
LGPerson
與LGPerson
的對比,使用+isisKindOfClass
- LGPerson(傳入
類
) vs LGPerson的元類即元類
LGPerson -- 不相等 - LGPerson(傳入
類
) vs 元類LGPerson的父類即根元類
-- 不相等 - LGPerson(傳入
類
) vs 根元類的父類即根類
-- 不相等 - LGPerson(傳入
類
) vs 根類的父類即nil
-- 不相等
- LGPerson(傳入
- re4 :0 降允,是
LGPerson
與LGPerson
的對比恩闻,使用+isMemberOfClass
- LGPerson(傳入
類
) vs元類
-- 不相等
- LGPerson(傳入
使用實例方法結(jié)果解析
- re5 :1 ,是
NSObject對象
與NSObject
的對比剧董,使用-isKindOfClass
- NSObject(傳入類幢尚,即
根類
) vs 對象的isa
即NSObject根類
-- 相等
- NSObject(傳入類幢尚,即
- re6 :1 ,是
NSObject對象
與NSObject
的對比翅楼,使用-isMemberOfClass
- NSObject(傳入類尉剩,即根類) vs 對象的類即NSObject根類 -- 相等
- re7 :1 ,是
LGPerson對象
與LGPerson
的對比毅臊,使用-isKindOfClass
- LGPerson(傳入類) vs 對象的
isa
即LGPerson -- 相等
- LGPerson(傳入類) vs 對象的
- re8 :1 理茎,是
LGPerson對象
與LGPerson
的對比,使用-isMemberOfClass
- LGPerson(傳入類) vs 對象的類即LGPerson -- 相等