美文网首页
关于isa走位的2道经典面试题

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

作者: iOS_沧海一笑 | 来源:发表于2020-09-17 14:46 被阅读0次

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

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

    • 我们首先创建一个类: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道经典面试题

          本文链接:https://www.haomeiwen.com/subject/mbhlyktx.html