美文网首页
OC底层原理09 - 经典面试题分析

OC底层原理09 - 经典面试题分析

作者: 卡布奇诺_95d2 | 来源:发表于2020-09-30 15:24 被阅读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 alloc] isKindOfClass:[NSObject class]];
            BOOL re4 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
            NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
    
            BOOL re5 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
            BOOL re6 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
            BOOL re7 = [(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;
    }
    

    isKindOfClass

    类方法isKindOfClass

    该方法主要是用来判断当前类的元类条件类是否相等。

    • 如果相等,返回YES;
    • 如果不相等,则继续查找该元类的父类,使之与条件类进行比较。
    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    实例方法isKindOfClass

    该方法主要是用来判断当前对象所属的类条件类是否相等。

    • 如果相等,则返回YES;
    • 如果不相等,则继续查找该类的父类,使之与条件类进行比较。
    - (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    示例1

    [(id)[NSObject class] isKindOfClass:[NSObject class]]

    1. 通过[NSObject class]分析得知,调用的是类方法isKindOfClass
    2. 第一次判断
      clsNSObject类
      tclsNSObject元类
      cls 不等于 tcls
      修改tclsNSObject元类的父类,即:NSObject类
    3. 第二次判断
      clsNSObject类
      tclsNSObject类
      cls 等于 tcls

    结果:返回YES

    示例2

    [(id)[LGPerson class] isKindOfClass:[LGPerson class]]

    1. 通过[LGPerson class]分析得知,调用的是类方法isKindOfClass
    2. 第一次判断
      clsLGPerson类
      tclsLGPerson元类
      cls 不等于 tcls
      修改tclsLGPerson元类的父类,即根元类NSObject
    3. 第二次判断
      clsLGPerson类
      tcls根元类
      cls 不等于 tcls
      修改tclsNSObject元类的父类,即:NSObject类
    4. 第三次判断
      clsLGPerson类
      tclsNSObject类
      cls 不等于 tcls
      修改tclsNSObject类的父类,即:nil
    5. 由于tclsnil,循环结束。

    结果:返回NO

    示例3

    [(id)[NSObject alloc] isKindOfClass:[NSObject class]]

    1. 通过[NSObject alloc]分析得知,调用的是对象方法isKindOfClass
    2. 第一次判断
      clsNSObject类
      tclsNSObject类
      cls 等于 tcls

    结果:返回YES

    示例4

    [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]

    1. 通过[LGPerson alloc]分析得知,调用的是对象方法isKindOfClass
    2. 第一次判断
      clsLGPerson类
      tclsLGPerson类
      cls 等于 tcls

    结果:返回YES

    isMemberOfClass

    类方法isMemberOfClass

    该方法主要用来判断当前类的元类条件类是否相等。

    • 如果相等,则返回YES;
    • 如果不相等,则返回NO;
    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    

    实例方法isMemberOfClass

    该方法主要用来判断当前对象所属的类条件类是否相等。

    • 如果相等,则返回YES;
    • 如果不相等,则返回NO;
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    

    示例5

    [(id)[NSObject class] isMemberOfClass:[NSObject class]]

    1. 通过[NSObject class]分析得知,调用的是类方法isMemberOfClass
    2. 当前元类NSObject元类条件类NSObject

    结果:不相等,返回NO

    示例6

    [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]

    1. 通过[LGPerson class]分析得知,调用的是类方法isMemberOfClass
    2. 当前元类LGPerson类的元类条件类LGPerson类

    结果:不相等,返回NO

    示例7

    [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]

    1. 通过[NSObject alloc]分析得知,调用的是实例方法isMemberOfClass
    2. 当前对象类NSObject类条件类NSObject类

    结果:相等,返回YES

    示例8

    [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]

    1. 通过[LGPerson alloc]分析得知,调用的是实例方法isMemberOfClass
    2. 当前对象类LGPerson类条件类LGPerson类

    结果:相等,返回YES

    面试题1输出结果:

    2020-09-30 14:59:08.857321+0800 KCObjc[72747:9401735]  
     re1 :1
     re2 :0
     re3 :1
     re4 :1
    2020-09-30 14:59:12.976812+0800 KCObjc[72747:9401735]  
     re5 :0
     re6 :0
     re7 :1
     re8 :1
    Program ended with exit code: 0
    

    总结

    1. 类方法 isKindOfClass 和 isMemberOfClass
      相同:比较 元类 与条件类。
      不同:isKindOfClass 在第一次比较不成功之后,会继续查找元类的父类,直至找到nil,而 isMemberOfClass 在比较不成功之后直接返回false。
    2. 实例方法 isKindOfClass 和 isMemberOfClass
      相同:比较 类与 条件类
      不同:isKindOfClass 在第一次比较不成功之后,会继续查找类的父类,直至找到nil,而 isMemberOfClass 在比较不成功之后直接返回false。

    面试题2

    创建一个 Person 类,继承自 NSObject,分别添加一个实例方法和一个类方法

    @interface LCPerson : NSObject
    
    - (void)sayHello;
    + (void)say666;
    
    @end
    
    @implementation LCPerson
    
    - (void)sayHello{
        NSLog(@"sayHello");
    }
    
    + (void)say666{
        NSLog(@"say666");
    }
    
    @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 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));
            
            NSLog(@"method1 :%p\n method2 :%p\n method3 :%p\n method4 :%p\n",method1,method2,method3,method4);
        }
        return 0;
    }
    

    class_getInstanceMethod

    该方法主要是用于获取实例方法如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL。针对该方法,苹果有如下说明

    image.png

    根据之前的分析,
    实例方法是存储在对象所属的类中
    类方法是存储元类中

    分析

    • method1
      目的:在LGPerson类中查找sayHello的实例方法
      依据:实例方法是存储在对象所属的类中
      实际:在LGPerson类中有定义sayHello的实例方法
      结果:method1是存在的。
    • method2
      目的:在LGPerson的元类中查找sayHello的实例方法
      依据:实例方法是存储在对象所属的类中
      实际:sayHello的实例方法是存在LGPerson类
      结果:method2是不存在的。
    • method3
      目的:在LGPerson类中查找sayHappy的实例方法
      依据:实例方法是存储在对象所属的类中
      实际:在LGPerson类中没有定义sayHappy的实例方法
      结果:method3是不存在的。
    • method4
      目的:在LGPerson的元类中查找sayHappy的实例方法
      依据:实例方法是存储在对象所属的类中
      实际:在LGPerson类中没有定义sayHappy的实例方法,但是由于LGPerson类定义了sayHappy的类方法,而类方法以实例方法的形式存储在元类中。
      结果:method4是存在的。

    面试题2输出结果:

    2020-09-30 15:01:41.051611+0800 KCObjc[72806:9404232] 
     method1 :0x100008100
     method2 :0x0
     method3 :0x0
     method4 :0x100008098
    Program ended with exit code: 0
    

    面试题3

    此题是对面试题2的延伸,因此准备条件与面试题2一样。
    分析以下代码分别输出什么?

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *person = [LGPerson alloc];
            Class pClass     = object_getClass(person);
            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));
            Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
            
            NSLog(@"method1 :%p\n method2 :%p\n method3 :%p\n method4 :%p\n",method1,method2,method3,method4);
        }
        return 0;
    }
    

    class_getClassMethod

    该方法主要是用于获取类方法,一个指向方法数据结构的指针,它指向类指定的类方法的实现如果指定的类或它的父类不包含具有指定的类方法,则为NULL。针对该方法,苹果有如下说明

    image.png

    分析

    Method class_getClassMethod(Class cls, SEL sel) {
        if (!cls  ||  !sel) return nil;
        return class_getInstanceMethod(cls->getMeta(), sel);
    }
    Class getMeta() {
            if (isMetaClass()) return (Class)this;
            else return this->ISA();
    }
    

    由源码知道,获取类方法其实就是获取元类的实例方法

    • method1
      传入的参数是LGPerson类,但在class_getClassMethod中,先根据LGPerson类获取LGPerson元类,然后再找LGPerson元类中的实例方法sel。此时selsayHello方法
      LGPerson元类中只存储了sayHappy 方法
      结果:method1是不存在的。

    • method2
      传入的参数是LGPerson元类,此时查找的是LGPerson元类中的实例方法sel。此时selsayHello方法
      LGPerson元类中只存储了sayHappy 方法
      结果:method2是不存在的。

    • method3
      传入的参数是LGPerson类,但在class_getClassMethod中,先根据LGPerson类获取LGPerson元类,然后再找LGPerson元类中的实例方法sel。此时selsayHappy方法
      LGPerson元类中存储了sayHappy 方法
      结果:method3是存在的。

    • method4
      传入的参数是LGPerson元类,此时查找的是LGPerson元类中的实例方法sel。此时selsayHappy方法
      LGPerson元类中存储了sayHappy 方法
      结果:method4是存在的。

    面试题3输出结果:

    2020-09-30 15:12:42.559984+0800 KCObjc[72925:9410227] 
     method1 :0x0
     method2 :0x0
     method3 :0x100008098
     method4 :0x100008098
    Program ended with exit code: 0
    

    总结

    • 实例方法存储在对象所属的类中
    • 类方法存储在元类中
    • 在元类中,类方法是以实例方法的形式存储的
    • 对象是所属类的实例
    • 类是元类的实例

    相关文章

      网友评论

          本文标题:OC底层原理09 - 经典面试题分析

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