第一道經(jīng)典面試題:類方法的歸屬分析
關于類方法和實例方法的歸屬分析,我們首先得知道:實例方法是在類中,而類方法是在元類中。下面我們通過一道面試題來驗證一下。
- 我們首先創(chuàng)建一個類:
LGPerson,繼承自NSObject,類里面有一個實例方法sayHello和一個類方法sayHappy。
@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)用LGPerson類
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
lgInstanceMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
}
return 0;
}
- 我們首先來分析一下
lgObjc_copyMethodList這個方法里面的實現(xiàn)
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);
// 打印信息:Method, name: sayHello
}
free(methods);
const char *classname = class_getName(pClass);
Class metaClass = objc_getMetaClass(classname);
Method method1 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%@--%@", NSStringFromSelector(method_getName(method1)), NSStringFromSelector(method_getName(method2)));
// 打印信息:(null)--sayHappy
}
- 通過第一個打印信息我們可以看出:
LGPerson類中獲取到的方法只有sayHello,而我們的類中明明有2個方法,還有一個類方法sayHappy怎么沒打印出來呢?- 我們接著看第二個打印信息:我們通過獲取
pClass也就是LGPerson的元類metaClass來獲取LGPerson中的2個方法,結(jié)果是獲取不到實例方法sayHello,但可以獲取到類方法sayHappy。- 通過這個方法我們足可以驗證
實例方法是在類中,類方法是在元類中。
- 接下類我們分析一下
lgInstanceMethod_classToMetaclass這個方法的實現(xiàn)
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);
// 打印信息:lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
}
- 通過打印信息我們可以得出:
1.method1是類中找到了sayHello方法,其地址為:0x1000031b0.
2.method2是元類中沒有找到sayHello方法,其地址為:0x0.
3.method3是類中沒有找到sayHappy方法,其地址為:0x0.
4.method4是元類中找到了sayHello方法,其地址為:0x100003148.
通過這個方法我們足可以驗證實例方法是在類中,類方法是在元類中。
- 接下來我們分析一下
lgIMP_classToMetaclass,方法的實現(xiàn)如下:
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
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);
// 打印信息:0x100000e60-0x1002c2240-0x1002c2240-0x100000e30
}
- 通過打印信息,我們發(fā)現(xiàn)為什么
imp2和imp3的地址一模一樣,而他們獲取的方法卻不同。并且imp1和imp4的地址不同,這是什么原因呢?分析這個,我們得從objc源碼分析了.
我們打斷點進入到class_getMethodImplementation源碼中:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
- 通過走源碼打斷點,我們發(fā)現(xiàn):
imp2和imp3進入到_objc_msgForward這個方法中,這個方法是消息轉(zhuǎn)發(fā)機制, 沒有返回imp的地址.imp2是元類獲取實例方法sayHello,因為sayHello是實例方法不在元類中,所以獲取不到imp, 就走到了_objc_msgForward方法中。同理:imp3是類中獲取類方法sayHappy, 由于類方法是在元類中不在類中,所以獲取不到imp,就走到了_objc_msgForward方法中。關于消息轉(zhuǎn)發(fā)機制_objc_msgForward我們后面會講到的。imp1是類中獲取到了實例方法sayHello,會返回imp.imp4是元類中獲取到了類方法sayHappy,也會返回imp.
第二道經(jīng)典面試題的解析:
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);
// 打印信息: 1 0 0 0
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);
// 打印信息:1 1 1 1
- 要解析這道題目,我們就必須要知道
isKindOfClass和isMemberOfClass的實例方法和類方法的isa走位圖了,下面我們通過進入objc的源碼和斷點走位來分析:
re1是NSObject類調(diào)用isKindOfClass的類方法,我們進入isKindOfClass類方法的源碼,打斷點發(fā)現(xiàn),居然不走這個類方法,而是走了objc_opt_isKindOfClass``這個方法,這是由于編譯器優(yōu)化了,實際和走isKindOfClass一樣的結(jié)果。由于是調(diào)用類方法,前面我們講到,類方法是在元類中,所以我們首先到元類找,有沒有NSObject,如果沒找到就再到元類的父類根元類找,如果還沒找到就到根元類的父類根類找,還沒有找到就到根類的父類nil找,可以得到和NSObject一致,所以返回YES```
+ (BOOL)isKindOfClass:(Class)cls {
// 類 vs 元類
// 根元類 vs NSObject
// NSObject vs NSObject
// LGPerson vs 元類 (根元類) (NSObject)
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
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);
}
re3是LGPerson調(diào)用isKindOfClass的類方法,也會走到objc_opt_isKindOfClass這個方法。LGPerson首先找到元類,再到根源類,根類,nil中找和LGPerson不一致,所以返回NO。re5是NSObject的實例方法調(diào)用isKindOfClass,也會走到objc_opt_isKindOfClass這個方法, 由于實例方法就在類中,所以直接可以在類中找到和NSObjct一致,返回YES.re7是LGPerson的實例方法調(diào)用isKindOfClass,也會走到objc_opt_isKindOfClass這個方法,同理,直接在類中就可以找到LGPerson,所以返回YES.
- 我們來看一下
isMemberOfClass的實例方法和類方法源碼:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
re2和re4由于都是調(diào)用isMemberOfClass的類方法,所以都會先找元類self->ISA()中和傳入的類cls相比較,re2中傳入的NSObject的根類和NSObject的元類不相等,所以返回NO。re4中傳入的LGPerson的根類和LGPerson的元類不相等,所以返回NO。re6和re8都是調(diào)用了isMemberOfClass的實例方法,所以會走[self class]方法和傳入的類cls相比較。由于傳入的類調(diào)用[self class]和傳入的cls是一樣的,所以都會返回YES。
- 最后附上一張
isa走位的流程圖:
isa流程圖.png
