关于isa走位的2道经典面试题

第一道经典面试题:类方法的归属分析

关于类方法和实例方法的归属分析,我们首先得知道:实例方法是在类中,而类方法是在元类中。下面我们通过一道面试题来验证一下。

  • 我们首先创建一个类: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函数中调用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这个方法里面的实现
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个方法,结果是获取不到实例方法sayHello,但可以获取到类方法sayHappy
  • 通过这个方法我们足可以验证实例方法是在类中,类方法是在元类中
  • 接下类我们分析一下lgInstanceMethod_classToMetaclass这个方法的实现
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,方法的实现如下:
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
}
  • 通过打印信息,我们发现为什么imp2imp3的地址一模一样,而他们获取的方法却不同。并且imp1imp4的地址不同,这是什么原因呢?分析这个,我们得从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;
}
  • 通过走源码打断点,我们发现:
  • imp2imp3 进入到 _objc_msgForward这个方法中,这个方法是消息转发机制, 没有返回imp的地址. imp2是元类获取实例方法sayHello,因为sayHello是实例方法不在元类中,所以获取不到imp, 就走到了 _objc_msgForward方法中。同理:imp3是类中获取类方法sayHappy, 由于类方法是在元类中不在类中,所以获取不到imp,就走到了_objc_msgForward方法中。关于消息转发机制_objc_msgForward我们后面会讲到的。
  • imp1是类中获取到了实例方法sayHello,会返回imp.
  • imp4是元类中获取到了类方法sayHappy,也会返回imp.

第二道经典面试题的解析:

        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  
  • 要解析这道题目,我们就必须要知道isKindOfClassisMemberOfClass的实例方法和类方法的isa走位图了,下面我们通过进入objc的源码和断点走位来分析:
  • re1NSObject类调用isKindOfClass的类方法,我们进入isKindOfClass类方法的源码,打断点发现,居然不走这个类方法,而是走了objc_opt_isKindOfClass``这个方法,这是由于编译器优化了,实际和走isKindOfClass一样的结果。由于是调用类方法,前面我们讲到,类方法是在元类中,所以我们首先到元类找,有没有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);
}
  • re3LGPerson调用isKindOfClass的类方法,也会走到objc_opt_isKindOfClass这个方法。LGPerson首先找到元类,再到根源类,根类,nil中找和LGPerson不一致,所以返回NO
  • re5NSObject的实例方法调用isKindOfClass,也会走到objc_opt_isKindOfClass这个方法, 由于实例方法就在类中,所以直接可以在类中找到和NSObjct一致,返回YES.
  • re7LGPerson的实例方法调用isKindOfClass,也会走到objc_opt_isKindOfClass这个方法,同理,直接在类中就可以找到LGPerson,所以返回YES.
  • 我们来看一下isMemberOfClass的实例方法和类方法源码:
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
  • re2re4由于都是调用isMemberOfClass的类方法,所以都会先找元类self->ISA()中和传入的类cls相比较,re2中传入的NSObject的根类和NSObject的元类不相等,所以返回NOre4中传入的LGPerson的根类和LGPerson的元类不相等,所以返回NO
  • re6re8都是调用了isMemberOfClass的实例方法,所以会走[self class]方法和传入的类cls相比较。由于传入的类调用[self class]和传入的cls是一样的,所以都会返回YES
  • 最后附上一张isa走位的流程图:
    isa流程图.png

你可能感兴趣的:(关于isa走位的2道经典面试题)