iOS中isa面试相关知识解析

isa走位图

前言

之前我们学习了类的相关知识和isa走位,为了加深印象,接下来我们通过两个例子来复习一下,这两个例子也是一下大厂可能出现的面试题

一、isKindOfClass和isMemberOfClass

void testISATachnology(){
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [(id)[WJPerson class] isKindOfClass:[WJPerson class]];
    BOOL re4 = [(id)[WJPerson class] isMemberOfClass:[WJPerson class]];
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [(id)[WJPerson alloc] isKindOfClass:[WJPerson class]];
    BOOL re8 = [(id)[WJPerson alloc] isMemberOfClass:[WJPerson class]];
    NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}

大家知道上面这段代码打印的结果是什么吗?
我们先来分析下题目,从上面代码中可以看到前4行代码调用的都是类方法,后4行都是对象方法。
我们先来分析一下+ (BOOL)isKindOfClass:(Class)cls类方法,我们看下苹果的源码实现

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

从上述代码中可以看出先找到本身的元类所以tcls为当前类的元类,然后用tclstcls的父类对比,如果一样则返回YES
我们先来分析下res1的结果

  • tclsNSObject的元类也就是根元类clsNSObject也就是根类
    得出结论tcls != cls,继续往下走,tcls根元类的父类也就是根类,此时tcls == cls,返回为YES

我们再来分析下res3的结果

  • tclsWJPerson的元类,clsWJPerson
    得出结论tcls != cls,继续往下走,tclsWJPerson元类的父类,也就是根元类
    得出结论tcls != cls,继续往下走,tcls根元类的父类也就是根类
    得出结论tcls != cls,继续往下走,tcls根类的父类为nil
    得出结论tcls != cls,此时跳出循环,返回NO

通过上面的分析我们可以得出结论:如果指定类是当前类的元类的父类则返回YES,否则返回NO

我们再来分析一下- (BOOL)isKindOfClass:(Class)cls对象方法,我们看下苹果的源码实现

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

从上述代码中可以得出结论:如果当前对象的类是指定类或其子类则返回YES,否则返回NO

从题目中得知re5re7中都是当前对象的类==指定类所以都返回为YES
接下来我们分析一下+ (BOOL)isMemberOfClass:(Class)cls这个类方法

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

从上述代码中可以得出结论:如果指定类是当前类的元类,则返回YES,否则返回NO

从题目中得知re2re4中都是指定类==当前类,所以都返回为NO
最后我们分析一下- (BOOL)isMemberOfClass:(Class)cls这个对象方法

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

从上述代码中可以得出结论:如果当前对象的类是指定类则返回YES,否则返回NO

从题目中得知re6re8中都是当前对象的类==指定类,所以都返回为YES
虽有我们可以推导出打印结果为 1 0 0 0 1 1 1 1
我们看下实际打印结果

2020-09-15 14:59:39.598314+0800 KCObjc[2299:91882]  re1 :1
 re2 :0
 re3 :0
 re4 :0
2020-09-15 14:59:39.601873+0800 KCObjc[2299:91882]  re5 :1
 re6 :1
 re7 :1
 re8 :1
总结
  • + (BOOL)isKindOfClass:(Class)cls:如果cls是当前类的元类的父类则返回YES,否则返回NO。
  • - (BOOL)isKindOfClass:(Class)cls:如果当前对象的类是cls或其子类则返回YES,否则返回NO。
  • + (BOOL)isMemberOfClass:(Class)cls:如果cls是当前类的元类,则返回YES,否则返回NO。
  • - (BOOL)isMemberOfClass:(Class)cls:如果当前对象的类是cls则返回YES,否则返回NO。

二、类的方法查找

@interface WJPerson : NSObject

- (void)sayHello;

+ (void)sayGoodbye;

@end

@implementation WJPerson

- (void)sayHello{
    NSLog(@"%s",__func__);
}

+ (void)sayGoodbye{
    NSLog(@"%s",__func__);
}

@end

我们先在类中定义一个对象方法,一个类方法。然后判断下面几段代码的打印结果。

一:class_getInstanceMethod探索

void instanceMethod_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(sayGoodbye));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayGoodbye));
    
    NSLog(@"%s - %d-%d-%d-%d",__func__,method1!=nil,method2!=nil,method3!=nil,method4!=nil);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        WJPerson *person = [WJPerson alloc];
        Class pClass     = object_getClass(person);
        instanceMethod_classToMetaclass(pClass);
    }
    return 0;
}

我们先看下class_getInstanceMethod的源码实现

/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

由方法的注释可以知道class_getInstanceMethod的作用是获取指定类和指定sel的实例方法
这里pClassWJPerson类,meteClassWJPerson的元类。
通过iOS底层之类结构分析这篇文章我们得知类中存放的是类的实例方法元类中存放的是类的类方法,注:类的类方法也可以叫做元类的实例方法。所以我们可以得出结论method1method4不为空,其它为空,所以推导出打印结果为 1 0 0 1
看下实际打印结果

instanceMethod_classToMetaclass - 1-0-0-1

二、class_getClassMethod探索

void classMethod_classToMetaclass(Class pClass){
    
    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(sayGoodbye));
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayGoodbye));
    
    NSLog(@"%s - %d-%d-%d-%d",__func__,method1!=nil,method2!=nil,method3!=nil,method4!=nil);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        WJPerson *person = [WJPerson alloc];
        Class pClass     = object_getClass(person);
        classMethod_classToMetaclass(pClass);
    }
    return 0;
}

我们先看下class_getClassMethod的源码实现

/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    /**
     * // NOT identical to this->ISA when this is a metaclass
     * Class getMeta() {
     *     if (isMetaClass()) return (Class)this;
     *     else return this->ISA();
     * }
     * 通过查看getMeta()实现发现,如果当前cls为元类则返回自身
     */
    return class_getInstanceMethod(cls->getMeta(), sel);
}

由方法的注释可以知道class_getClassMethod的作用是获取指定类和指定sel的类方法。但是进一步观察发现实际上是获取指定类的元类和指定sel的实例方法.
这里pClassWJPerson类,meteClassWJPerson的元类。

  • method1是获取WJPerson的元类的sayHello方法,而sayHello为是WJPerson类的实例方法,所以获取不到,method1为nil,同理method2也为nil
  • method3是获取WJPerson的元类的sayGoodbye方法,sayGoodbyeWJPerson的元类的实例方法,所以method3不为nil,同理method4也不为nil
    所以我们可以得出结论method3method4不为空,其它为空,所以推导出打印结果为 0 0 1 1
    看下实际打印结果
classMethod_classToMetaclass - 0-0-1-1

三、class_getMethodImplementation探索

void iMP_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(sayGoodbye));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayGoodbye));

    NSLog(@"%s - %d-%d-%d-%d",__func__,imp1!=nil,imp2!=nil,imp3!=nil,imp4!=nil);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        WJPerson *person = [WJPerson alloc];
        Class pClass     = object_getClass(person);
        iMP_classToMetaclass(pClass);
    }
    return 0;
}

我们先看下class_getMethodImplementation的源码实现

/** 
 * Returns the function pointer that would be called if a 
 * particular message were sent to an instance of a class.
 * 
 * @param cls The class you want to inspect.
 * @param name A selector.
 * 
 * @return The function pointer that would be called if \c [object name] were called
 *  with an instance of the class, or \c NULL if \e cls is \c Nil.
 *
 * @note \c class_getMethodImplementation may be faster than \c method_getImplementation(class_getInstanceMethod(cls, name)).
 * @note The function pointer returned may be a function internal to the runtime instead of
 *  an actual method implementation. For example, if instances of the class do not respond to
 *  the selector, the function pointer returned will be part of the runtime's message forwarding machinery.
 */
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;
}

由方法的注释可以知道class_getMethodImplementation的作用是获取指定类的指定方法实现
这里pClassWJPerson类,meteClassWJPerson的元类。

  • imp1是获取WJPerson的类的sayHello方法实现,sayHello为是WJPerson类的实例方法,且已经进行了实现,所以可以获取到,imp1为-[WJPerson sayHello]
  • imp2是获取WJPerson的元类的sayHello方法实现,而sayHello为是WJPerson类的实例方法,虽然进行了实现,但并不能在WJPerson的元类中查找到实现,这是会进入消息转发流程,返回的是libobjc.A.dylib _objc_msgForward
  • imp3是获取WJPerson类的sayGoodbye方法,sayGoodbyeWJPerson的元类的实例方法,虽然进行了实现,但并不能在WJPerson的类中查找到实现,此时会进入消息转发流程,返回的是libobjc.A.dylib _objc_msgForward
  • imp1是获取WJPerson的元类的sayGoodbye方法实现,sayGoodbye为是WJPerson的元类的实例方法,且已经进行了实现,所以可以获取到,imp4为+[WJPerson sayGoodbye]
    所以我们可以得出结论imp1imp4都不为空,所以推导出打印结果为 1 1 1 1
    看下实际打印结果
iMP_classToMetaclass - 1-1-1-1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341