iOS isa理解测试:实例方法,类方法,iskindof,isMemberof问题

面试题一:考察实例方法和类方法

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif

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

@implementation LGPerson

- (void)sayHello{
    LGLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
    LGLog(@"LGPerson say : Happy!!!");
}
@end

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); // 1
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); // 0

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy)); // 0
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));// 1
    
    LGLog(@"%s===%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void lgClassMethod_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(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s===%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

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

    LGLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    LGLog(@"%s",__func__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        LGLog(@"Hello, World!");
    }
    return 0;
}

对于这道题首先一点我们要知道OC里面方法的存储是跟isa的走位图紧密相连的;

1、OC对象的实例方法存储类对象
2、OC对象的类方法存储在元类对象

isa流程图.png

这的两个方法为
- (void)sayHello: 实例方法
+ (void)sayHappy:类方法
为了不误导大家我先直接打印答案看看

答案1

结合isa走位图,根据答案分析,

  • 1、 在函数lgInstanceMethod_classToMetaclass(Class pClass)

    • Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); 能找到方法method1 ,符合预期
    • Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); 不能找到方法method2 ,符合预期
    • Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));不能找到方法method3 ,符合预期
    • Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));能找到方法method4 符合预期
  • 2、在函数lgClassMethod_classToMetaclass(Class pClass)

    • Method method1 = class_getClassMethod(pClass, @selector(sayHello)); 不能找到方法method1 ,符合预期
    • Method method2 = class_getClassMethod(metaClass, @selector(sayHello)); 不能找到方法method2 ,符合预期
    • Method method3 = class_getClassMethod(pClass, @selector(sayHappy));能找到方法method3 , 符合预期
    • Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));在这里我需要先指出正确答案: 能找到方法;奇了怪,但是按照我们 isa的走位分析,这里应该去元类的元类(根元类)里面找sayHappy了啊,应该找不到啊,我们不妨看看class_getClassMethod源码
//objc-class.mm
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
//.....
//objc_rumtime-new.h
// NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

我们可以清晰的看到class_getClassMethod方法其实实质上是调用的class_getInstanceMethod,并且是找Class元类;而元类的搜索逻辑是:如果当前类已经是元类,则返回当前类。这就很好解释了为什么在元类里面找类方法能找到了。

  • 3、 在函数lgIMP_classToMetaclass(Class pClass)
    • IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello)); 能找到方法的实现imp1 ,符合预期
    • IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello)); 居然找到的方法的实现,我们前面都在函数lgInstanceMethod_classToMetaclass中分析打印了Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));的结果:不能找到方法。现在怎么找到了这里方法的实现imp2呢?我们看看class_getMethodImplementation源码
//objc_class.mm 文件
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;
}
// ...
// message.h 文件
OBJC_EXPORT void
_objc_msgForward(void /* id receiver, SEL sel, ... */ ) 
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
// ...
//objc-msg-arm.s 文件
    STATIC_ENTRY __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret

    beq __objc_msgForward
    b   __objc_msgForward_stret
    
    END_ENTRY __objc_msgForward_impcache
    

    ENTRY __objc_msgForward
    // Non-stret version

在这里我们可以很清晰的看见:***当lookUpImpOrNil找不到方法实现的时候,直接返回_objc_msgForward的函数指针,而这个·_objc_msgForward`的实现是在汇编里面的,所以必定存在;

  • IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));,根据前面的分析,能找到方法imp3, 应该为_objc_msgForward的实现
  • IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));能找到方法实现imp4;

面试题二:考察iskindof,isMemberof理解

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface LGPerson : NSObject
@end

@implementation LGPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
        NSLog(@"\n 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)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
        NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
   
    }
    return 0;
}

首先我们来看看结果

答案2

首先我们需要对isKindOfClassisMemberOfClass这两个方法有所了解
我们先来看看因为解释

  • - (BOOL)isKindOfClass:(Class)aClass;
    Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.

    大意: 判断当前接收者(这里可以理解为调用者)是否是给出类(这里可以理解为传入的aClass参数)或者其子类的实例。

  • - (BOOL)isMemberOfClass:(Class)aClass;
    Returns a Boolean value that indicates whether the receiver is an instance of a given class.
    大意:判断当前接收者是否是给出类的实例。

对于类方法


  • 【1】、[(id)[NSObject class] isKindOfClass:[NSObject class]]; 我们知道[NSObject class]会调用类方法+class;我们查看源码得知
- (Class)class {
    return object_getClass(self);
}
+ (Class)class {
    return self;
}

返回的是当前类自己

Note:其实在真正运行的时候,llvm针对id类型会做一层处理调用的真正的实现函数是如下逻辑,当发现cls->hasCustomCore()false时,直接返回当前obj或者obj->isa指向的Class

// 源码文件NSObject.mm文件中可以找到
  // Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}

所以当我们调用实例方法类方法 class时返回的都是当前的类对象;

接下到我们的isKindof这个实例方法;为什么是实例方法呢?因为我们将它强转成了id类型,所以我们看看源码

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

Note:
1、我们在源码文件NSObject.mm发现了+isKindOfClass的类方法,这个NSObject类方法是存贮在根元类里面的, 在我们面试题中,其本质是Class类型,只是强转成了id类型,当调用isKindOfClass方法时,其实是会去Class指向的元类里面查找方法, 即会调用+isKindOfClass;不过在正式环境中我们是看不到这个类方法的,仅能看到实例方法
2、注意重点:其实这个方法也是忽悠人的,经过查看llvm源码发现iskindOfclass经过了优化处理,其真实会调用的方法是

// 源码文件NSObject.mm文件中可以找到
// Calls [obj 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->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

很显然当第一来的时候objNSObject类对象,clstcls根元类,很明显当前是tcls == otherClass不成立,当我们第二次来的时候tcls去取他的父类赋值到tcls,根据isa走位图,我们知道根元类的父类是NSObject的类对象;所以此时tcls == otherClass 成立返回为true

  • 【2】、[(id)[NSObject class] isMemberOfClass:[NSObject class]];对于isMemberOf这个方法源码去看看;
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

这里就相当简单了:直接判断当前的实例的类对象是否等于传入的cls
当我们调用isMemberOfClass方法时,self就是[NSObject class]经过强转之后的id类型(其本质就是NSObject类对象),当经过[self class]取class操作之后,会变为NSObject元类对象NSObject类对象比较,所以为false

  • 【3】、[(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 这里最终是LGPerson类对象LGPerson元类对象或者其父类的比较,为false
  • 【4】 、[(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; 【3】都是false,这里还是它的子集,更加为false;
  • 【5】、[(id)[NSObject alloc] isKindOfClass:[NSObject class]]; 这里简单分析true;
  • 【6】、[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; 这里简单分析true;
  • 【7】、[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 这里简单分析true;
  • 【8】、[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; 这里简单分析true;

两道面试题的总结:

需要充分理解isa的走位图:方法的存储和类型的继承。

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