OC-经典面试题分析(一)

几道isa相关的经典面试题以及分析

前期准备:

@interface HRTest : NSObject
    - (void)sayHello;
    + (void)sayHappy;
@end

方法归属相关测试

第一道

HRTest *test = [HRTest alloc];
        Class pClass     = object_getClass(test);

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(@"%p-%p-%p-%p",method1,method2,method3,method4);

输出结果:

  • class_getInstanceMethod获取当前类的实例方法
  • 实例方法存在类中,类方法存在元类中

第二道

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(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);

输出结果:


  • class_getClassMethod获取当前类的类方法
  • 前三个都很好理解:sayHello是实例方法,不可能找到,sayHappy是类方法,自然存在于当前类中。可是当前类的元类中不可能存在该类方法,查看该方法实现的源码。
class_getClassMethod的方法实现
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();
}
  • 看到这里有木有忽然开朗,其实类的类方法在底层中的存在形式:元类中的实例方法。(c、c++中并没有实例、类方法)
  • 第四步判断存在的真正原因是:碰到元类会直接返回当前元类,并不会继续查找向上查找(元类的isa指向根源类)
  • metaClass已经是元类了,无需找到根源类了;第三步、第四步等价。
小总结

实例方法存在类中,类方法存在元类中。

第三道

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));
  • 第二步、第三步中为什么会存在呢?而且地址是相同的!还是先看一下源码是如何实现的.
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;
    if (!cls  ||  !sel) return nil;
    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);   //先找缓存cache_t,然后去cls->data()->methods()中查找
    if (!imp) {
        return _objc_msgForward;    //找不到回到这里
    }
    return imp;
}
  • 通过源码发现方法查找先是从cache_t中找,然后去cls->data()->methods()中查找,最后会进入_objc_msgForward流程.
0x100001d00-0x7fff6c018580-0x7fff6c018580-0x100001d30

(lldb) p/x imp1
(IMP) $0 = 0x0000000100001d00 (002-类方法归属分析`-[LGPerson sayHello] at LGPerson.m:13)

(lldb) p/x imp4
(IMP) $1 = 0x0000000100001d30 (002-类方法归属分析`+[LGPerson sayHappy] at LGPerson.m:17)

(lldb) p/x imp2
(IMP) $2 = 0x00007fff6c018580 (libobjc.A.dylib`_objc_msgForward)
  • 通过打印得知,0x00007fff6c018580不该存在的这个地址是:_objc_msgForward流程!!并不是找到真的方法!!!

第四道

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];   
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; 
        BOOL re3 = [(id)[HRTest class] isKindOfClass:[HRTest class]];  
        BOOL re4 = [(id)[HRTest class] isMemberOfClass:[HRTest class]];

输出结果:


  • +isKindOfClass类方法:
+ (BOOL)isKindOfClass:(Class)cls {
    //使用调用者的isa和当前入参对比。
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
2021.6.11补充

事实上这个方法并不会被调用,而是由llvm处理为调用objc_opt_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->getSuperclass()) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

循环:
第一次循环:NSObject的isa -> 根元类 与 NSObject不相同
第二次循环:根元类的superclass -> NSObject 与NSObject相同
所以是通过根元类的父类又回到NSObject这条线找到的,其他类没有这个特性。

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

拿当前类的isa指向和参数做对比,相同的类一定不相等.

第五道

BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL re7 = [(id)[HRTest alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[HRTest alloc] isMemberOfClass:[LGPerson class]]; 
  • -isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • -isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

这道题很简单,现实面试应该是碰不到这样的题目。

2021.6.11补充 class方法的源码实现
+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • 对于类来说,就是返回自己
  • 对于实例对象来说,调用class就是通过isa找类。

第六道

objc_object 、objc_class、 NSObject、类之间的关系

  • NSObjectOC中对象的基类;objc_objectC++/C中对象的基类,更偏向底层。
  • 万物皆对象,而对象都是继承自objc_object,包括objc_class
  • objc_class是所有的基类

第七道

属性 、 成员变量 、 实例变量区别

  • 属性(property):系统自动生成:带下划线成员变量 + setter + getter方法

  • 成员变量(ivar):开发者在{}中定义的变量。由实例变量+非实例变量组成

  • 实例变量:可以被实例化的变量,是一种特殊的成员变量;例如 NSObject、UILabel、UIButton等

  • 非实例变量:不可以被实例化的变量;列如:int、float等基本数据类型

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