美文网首页iOS底层原理
iOS底层原理 06 : isa走位&类继承的经典面试题

iOS底层原理 06 : isa走位&类继承的经典面试题

作者: smooth_lgh | 来源:发表于2020-09-15 18:29 被阅读0次
    1. 下面代码打印的结果是什么,并做分析。
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];  
            BOOL re2 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     
            BOOL re3 = [(id)[NSObject class] isMemberOfClass:[NSObject 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);
        }
        return 0;
    }
    

    打印的结果:

    re1 :1
    re2 :0
    re3 :0
    re4 :0
    

    为什么是这个结果呢?
    ** [[NSObject class] isKindOfClass:[NSObject class]],首先前面是在调用isKindOfClass方法,所以找的是+ (BOOL)isKindOfClass:(Class)cls类方法。**
    我们查看源码

    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    对于re1

    1.Class tcls = self->ISA(), NSObject类isa指向根元类根元类 != NSObject类,循环继续
    2.接下来tcls = tcls->superclass根元类的父类NSObject类, 此时 tcls == cls ,return YES;

    对于re2 **

    1.Class tcls = self->ISA(), LGPerson信息类isa指向LGHPerson的元类LGPerson的元类!= LGPerson类,循环继续。
    2. tcls = tcls->superclassLGPerson的元类的父类根元类根元类 != LGPerson类,循环继续。
    3. tcls = tcls->superclass根元类 的父类NSObject类NSObject类 != LGPerson类,循环继续。
    4. tcls = tcls->superclassNSObject的父类nil,循环结束
    5. return NO;

    同理[(id)[NSObject class] isMemberOfClass:[NSObject class]],我们需要找的是+ (BOOL)isMemberOfClass:(Class)cls类方法。

    通过源码得出: +isMemberOfClass是判断当前类的isa所指向的类传入的cls类是否一致

    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    

    对于re3

    NSObject类的isa指向根元类根元类 != NSObject类,return NO;

    对于re4

    LGPerson类的isa指向LGPerson的元类LGPerson的元类 != LGPerson类, return NO;

    2. 下面代码打印的结果是什么,并做分析。
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];        
            BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 
            BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject 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);
        }
        return 0;
    }
    

    打印的结果:

    re5 :1
    re6 :1
    re7 :1
    re8 :1
    

    首先[(id)[NSObject alloc] isKindOfClass:[NSObject class]],我们应该找的是实例方法-(BOOL)isKindOfClass:(Class)cls
    查看源码:
    1. 首先是tcls = [self class]cls比较,
    2. 接下来tcls->superclass(即父类)cls比较,
    3. tcls->superclass->superclass(即父类的父类) 与cls比较
    .....for循环
    4. 一直到NSObject类cls比较,如果还是不相等,NSObject类->superclassnil,for循环结束,返回NO

    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    对于re5

    Class tcls = [self class] ,tcls=NSObject类,NSObject类=NSObject类, return YES;

    对于re6

    Class tcls = [self class] ,tcls=LGPerson类,LGPerson类==LGPerson类,return YES;

    对于[(id)[NSObject alloc] isMemberOfClass:[NSObject class]],我们该找的是实例方法-(BOOL)isMemberOfClass:(Class)cls*
    查看源码:

    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    

    对于re7

    Class tcls = [self class]tcls=NSObject类NSObject类==NSObject类, return YES;

    对于re8

    Class tcls = [self class] ,tcls=LGPerson类LGPerson类==LGPerson类,return YES;

    2.以下代码的输出结果是什么?并分析原因。
    @interface LGPerson : NSObject
    - (void)sayHello;
    + (void)sayHappy;
    
    @end
    
    @implementation LGPerson
    
    - (void)sayHello{
        NSLog(@"LGPerson say : Hello!!!");
    }
    
    + (void)sayHappy{
        NSLog(@"LGPerson say : Happy!!!");
    }
    @end
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *person = [LGPerson alloc];
            Class pClass     = object_getClass(person);
            const char *className = class_getName(pClass);
            Class metaPClass = objc_getMetaClass(className);
            
            Method method1 = class_getClassMethod(pClass, @selector(sayHello));
            Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
            Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
            Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
            LGLog(@"method1=%p",method1);
            LGLog(@"method2=%p",method2);
            LGLog(@"method3=%p",method3);
            LGLog(@"method4=%p",method4);
            
            Method method5 = class_getInstanceMethod(pClass, @selector(sayHello));
            Method method6 = class_getInstanceMethod(metaPClass, @selector(sayHello));
            Method method7 = class_getInstanceMethod(pClass, @selector(sayHappy));
            Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
            LGLog(@"method5=%p",method5);
            LGLog(@"method6=%p",method6);
            LGLog(@"method7=%p",method7);
            LGLog(@"method8=%p",method8);
        }
        return 0;
    }
    

    打印结果:

    method1=0x0
    method2=0x0
    method3=0x100003070
    method4=0x100003070
    method5=0x1000030d8
    method6=0x0
    method7=0x0
    method8=0x100003070
    

    首先我们知道, 实例方法存储在类的实例方法列表中中,而类方法存储在元类的实例方法列表中。

    查看class_getClassMethod的源代码.
    从源码得出:
    1. 首先它会去找cls的元组实例方法列表,结合getMeta()方法,如果本身是元组类型,直接去找自身的实例方法列表,
    2. 然后cls->getMeta()->superclass,去cls的元组的父类里面找,
    ..... for循环..... ,
    3. 一直找到根元类,然后是NSObject类,最后是nil,结束循环。

    Method class_getClassMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
     
    // 如果是元组类型,直接返回,否则返回当前class的isa指向的类
    Class getMeta() {
            if (isMetaClass()) return (Class)this;
            else return this->ISA();
        }
    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
        return _class_getMethod(cls, sel);
    }
    static Method _class_getMethod(Class cls, SEL sel)
    {
        mutex_locker_t lock(runtimeLock);
        return getMethod_nolock(cls, sel);
    }
    getMethod_nolock(Class cls, SEL sel)
    {
        method_t *m = nil;
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
        while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
            cls = cls->superclass;
        }
        return m;
    }
    

    class_getClassMethod源码大致流程图:

    class_getClassMethod源码大致流程图.png

    sayHello实例方法,所以存在于LGPerson类的实例方法列表
    1. method1,所以在LGPerson元类的methods中获取不到,返回0x00
    2. method2, sayHello是实例方法,所以在LGPerson元类的methods中就更加获取不到,返回0x00

    sayHappy类方法,所以存在于LGPerson元类的实例方法列表中.
    1. method3, 会去找LGPerson元类的实例方法列表中寻找,恰好就存在sayHappy,所以method3有值
    2. method4, 也是会在会在找LGPerson元类的实例方法列表中寻找,所以method4有值

    接下来我们看class_getInstanceMethod的源码.
    从源码得出:先从cls类的methods列表中查找是否存在sel,然后从cls类的父类的methods列表查找.... 直到从NSObject类的methods列表查找

    Method class_getInstanceMethod(Class cls, SEL sel)
    {
        if (!cls  ||  !sel) return nil;
        lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
        return _class_getMethod(cls, sel);
    }
    
    static Method _class_getMethod(Class cls, SEL sel)
    {
        mutex_locker_t lock(runtimeLock);
        return getMethod_nolock(cls, sel);
    }
    static method_t *
    getMethod_nolock(Class cls, SEL sel)
    {
        method_t *m = nil;
        runtimeLock.assertLocked();
        ASSERT(cls->isRealized());
        while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
            cls = cls->superclass;
        }
        return m;
    }
    
    class_getInstanceMethod的源码流程图.png

    sayHello是实例方法,存在于LGPerson类的methods列表中。
    所以 method05有值,method6是0x00。

    sayHappy是类方法,存在于LGPerson元类的methods列表中.
    所以 method07是0x00,method8有值

    【拓展】3.以下代码输出的结果是什么?
    @interface LGPerson : NSObject
    - (void)sayHello;
    + (void)sayHappy;
    
    @end
    
    @implementation LGPerson
    
    - (void)sayHello{
        NSLog(@"LGPerson say : Hello!!!");
    }
    
    + (void)sayHappy{
        NSLog(@"LGPerson say : Happy!!!");
    }
    @end
    
    @interface LGTeacher : LGPerson
    - (void)say666;
    @end
    
    @implementation LGTeacher
    - (void)say666{
        
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGTeacher *teacher = [LGTeacher alloc];
            Class pClass     = object_getClass(teacher);
            const char *className = class_getName(pClass);
            Class metaPClass = objc_getMetaClass(className);
            
            Method method1 = class_getClassMethod(pClass, @selector(sayHello));
            Method method2 = class_getClassMethod(metaPClass, @selector(sayHello));
            
            Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
            Method method4 = class_getClassMethod(metaPClass, @selector(sayHappy));
            
            Method method5 = class_getClassMethod(pClass, @selector(say666));
            Method method6 = class_getClassMethod(metaPClass, @selector(say666));
            LGLog(@"method1=%p",method1);
            LGLog(@"method2=%p",method2);
            LGLog(@"method3=%p",method3);
            LGLog(@"method4=%p",method4);
            LGLog(@"method5=%p",method5);
            LGLog(@"method6=%p",method6);
            
            Method method7 = class_getInstanceMethod(pClass, @selector(sayHello));
            Method method8 = class_getInstanceMethod(metaPClass, @selector(sayHello));
            
            Method method9 = class_getInstanceMethod(pClass, @selector(sayHappy));
            Method method10 = class_getInstanceMethod(metaPClass, @selector(sayHappy));
            
            Method method11 = class_getInstanceMethod(pClass, @selector(say666));
            Method method12 = class_getInstanceMethod(metaPClass, @selector(say666));
            
            LGLog(@"method5=%p",method7);
            LGLog(@"method6=%p",method8);
            LGLog(@"method7=%p",method9);
            LGLog(@"method8=%p",method10);
            LGLog(@"method7=%p",method11);
            LGLog(@"method8=%p",method12);
        }
         return 0;
    }
    
    

    首先sayHello是实例方法,存在于LGPerson类的methods列表中。

    对于method1,class_getClassMethod(LGTeacher, @selector(sayHello)) ,
    1. 首先去LGTeacher的元类里面找,没有找到,
    2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面去找,没有找到,
    3. 之后去根元类NSObject类寻找,更是不会找到,返回0x00

    对于method2 ,class_getClassMethod(LGTeacher的元类, @selector(sayHello)),
    1. 因为传入的是LGTeacher的元类,所以直接到LGTeacher的元类里面找,没有找到,
    2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面去找,没有找到,
    3. 之后去根元类NSObject类寻找,更是不会找到,返回0x00。

    首先sayHappy是类方法,存在于LGPerson元类的methods列表中。

    对于method3 ,class_getClassMethod(LGTeacher, @selector(sayHappy)),
    1. 首先去LGTeacher的元类里面实例方法列表中找,没有找到,
    2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面的实例方法列表中寻找,可以找到,所以method3有值

    对于method4来说 ,
    1. 首先去LGTeacher的元类里面实例方法列表中找,没有找到,
    2. 接下来去LGTeacher的元类的superclass,即LGPerson的元类里面的实例方法列表中寻找,可以找到,所以method4有值

    首先say666是实例方法,存在于LGTeacher类的实例方法列表中

    所以method5 和 method6都为 0x00.

    首先sayHello是实例方法,存在于LGPerson类的实例方法列表

    对于method7 来说,
    1. 去LGTeacher类的实例方法列表中找,没有找到,
    2. 然后去LGPerson类的实例方法列表中找,找到了,method7有值。

    对于method8 ,
    1. 去LGTeacher元类的实例方法列表中找,没有找到,
    2. 然后去LGPerson元类的实例方法列表中找,没有找到,
    3. 最后去根元类和NSObject类的实例方法列表寻找,没有找到,method8=0x00

    首先sayHappy是类方法,存在于LGPerson元类的实例方法列表中。

    对与method9,
    1. 去LGTeacher类的实例方法列表中找,没有找到,
    2. 然后去LGPerson类的实例方法列表中找,没有找到了,3. 最后去NSObject类的实例方法列表寻找,没有找到,method9=0x00

    对与method10,
    1. 去LGTeacher元类的实例方法列表中找,没有找到,
    2. 然后去LGPerson元类的实例方法列表中找,找到了,method10有值

    首先say666是实例方法,存在于LGTeacher类的实例方法列表

    所以method11有值,method12=0x00

    **打印结果: **

    method1=0x0
    method2=0x0
    method3=0x100003070
    method4=0x100003070
    method5=0x0
    method6=0x0
    
    method7=0x1000030d8
    method8=0x0
    method9=0x0
    method10=0x100003070
    method11=0x100003188
    method12=0x0
    
    4.下面的代码输出什么?
    @implementation Son : Father
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    

    结果: Son / Son

    分析:

    对于上面的答案,第一个的结果应该是我们的预期结果,但是第二个结果却让我们很费解了。

    那我们利用前面文章讲过的知识点来分析一下整个的流程。

    因为,Son 及 Father 都没有实现 -(Class)calss 方法,所以这里所有的调用最终都会找到基类 NSObject 中,并且在其中找到 -(Class)calss 方法。那我们需要了解的就是在 NSObject 中这个方法的实现了。

    在 NSObject.mm 中可以找到 -(Class)class 的实现:

    - (Class)class {
        return object_getClass(self);
    }
    

    在 objc_class.mm 中找到 object_getClass 的实现:

    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    

    ps:上面的方法定义可以去可编译调试的objc4源码中下载源码哦。

    可以看到,最终这个方法返回的是,调用这个方法的 objc 的 isa 指针。那我们只需要知道在题干中的代码里面最终是谁在调用 -(Class)class 方法就可以找到答案了。

    接下来,我们利用 clang -rewrite-objc 命令,将题干的代码转化为如下代码:

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8k_cgm28r0d0bz94xnnrr606rf40000gn_T_Car_3f2069_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Car"))}, sel_registerName("class"))));
    

    从上方可以得出,调用 Father class 的时候,本质是在调用

    objc_msgSendSuper(struct objc_super *super, SEL op, ...)
    

    struct objc_super 的定义如下:

    struct objc_super {
        /// Specifies an instance of a class.
        __unsafe_unretained _Nonnull id receiver;
    
        /// Specifies the particular superclass of the instance to message. 
    #if !defined(__cplusplus)  &&  !__OBJC2__
        /* For compatibility with old objc-runtime.h header */
        __unsafe_unretained _Nonnull Class class;
    #else
        __unsafe_unretained _Nonnull Class super_class;
    #endif
        /* super_class is the first class to search */
    };
    

    从定义可以得知:当利用 super 调用方法时,只要编译器看到super这个标志,就会让当前对象去调用父类方法,本质还是当前对象在调用,是去父类找实现,super 仅仅是一个编译指示器。但是消息的接收者 receiver 依然是self。最终在 NSObject 获取 isa 指针的时候,获取到的依旧是 self 的 isa,所以,我们得到的结果是:Son。

    5. 看看下方的代码会输出什么?
    @interface Father : NSObject
    @end
    
    @implementation Father
    
    - (Class)class {
        return [Father class];
    }
    
    @end
    
    ---
    
    @interface Son : Father
    @end
    
    @implementation Son
    
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        Son *foo = [[Son alloc]init];
        return 0;
    }
    
    ---输出:---
    Father
    Father
    

    更多的面试题和答案:https://github.com/iOSputao/iOS-

    相关文章

      网友评论

        本文标题:iOS底层原理 06 : isa走位&类继承的经典面试题

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