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

面试题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

总结

  • 实例方法存储在对象所属的类中
  • 类方法存储在元类中
  • 在元类中,类方法是以实例方法的形式存储的
  • 对象是所属类的实例
  • 类是元类的实例
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354