iOS isa理解測(cè)試:實(shí)例方法舰讹,類(lèi)方法茅姜,iskindof,isMemberof問(wèn)題

面試題一:考察實(shí)例方法和類(lèi)方法

#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;
}

對(duì)于這道題首先一點(diǎn)我們要知道OC里面方法的存儲(chǔ)是跟isa的走位圖緊密相連的;

1月匣、OC對(duì)象的實(shí)例方法存儲(chǔ)類(lèi)對(duì)象
2钻洒、OC對(duì)象的類(lèi)方法存儲(chǔ)在元類(lèi)對(duì)象

isa流程圖.png

這的兩個(gè)方法為
- (void)sayHello: 實(shí)例方法
+ (void)sayHappy:類(lèi)方法
為了不誤導(dǎo)大家我先直接打印答案看看

答案1

結(jié)合isa走位圖,根據(jù)答案分析锄开,

  • 1素标、 在函數(shù)lgInstanceMethod_classToMetaclass(Class pClass)

    • Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); 能找到方法method1 ,符合預(yù)期
    • Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); 不能找到方法method2 萍悴,符合預(yù)期
    • Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));不能找到方法method3 头遭,符合預(yù)期
    • Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));能找到方法method4 符合預(yù)期
  • 2、在函數(shù)lgClassMethod_classToMetaclass(Class pClass)

    • Method method1 = class_getClassMethod(pClass, @selector(sayHello)); 不能找到方法method1 癣诱,符合預(yù)期
    • Method method2 = class_getClassMethod(metaClass, @selector(sayHello)); 不能找到方法method2 计维,符合預(yù)期
    • Method method3 = class_getClassMethod(pClass, @selector(sayHappy));能找到方法method3 , 符合預(yù)期
    • Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));在這里我需要先指出正確答案: 能找到方法;奇了怪撕予,但是按照我們 isa的走位分析鲫惶,這里應(yīng)該去元類(lèi)的元類(lèi)(根元類(lèi))里面找sayHappy了啊,應(yīng)該找不到啊嗅蔬,我們不妨看看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方法其實(shí)實(shí)質(zhì)上是調(diào)用的class_getInstanceMethod,并且是找Class元類(lèi);而元類(lèi)的搜索邏輯是:如果當(dāng)前類(lèi)已經(jīng)是元類(lèi),則返回當(dāng)前類(lèi)剑按。這就很好解釋了為什么在元類(lèi)里面找類(lèi)方法能找到了。

  • 3澜术、 在函數(shù)lgIMP_classToMetaclass(Class pClass)
    • IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello)); 能找到方法的實(shí)現(xiàn)imp1 艺蝴,符合預(yù)期
    • IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello)); 居然找到的方法的實(shí)現(xiàn),我們前面都在函數(shù)lgInstanceMethod_classToMetaclass中分析打印了Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));的結(jié)果:不能找到方法∧穹希現(xiàn)在怎么找到了這里方法的實(shí)現(xiàn)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

在這里我們可以很清晰的看見(jiàn):***當(dāng)lookUpImpOrNil找不到方法實(shí)現(xiàn)的時(shí)候,直接返回_objc_msgForward的函數(shù)指針,而這個(gè)·_objc_msgForward`的實(shí)現(xiàn)是在匯編里面的,所以必定存在;

  • IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));,根據(jù)前面的分析缩擂,能找到方法imp3, 應(yīng)該為_objc_msgForward的實(shí)現(xiàn)
  • IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));能找到方法實(shí)現(xiàn)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;
}

首先我們來(lái)看看結(jié)果

答案2

首先我們需要對(duì)isKindOfClassisMemberOfClass這兩個(gè)方法有所了解
我們先來(lái)看看因?yàn)榻忉?p>

  • - (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.

    大意: 判斷當(dāng)前接收者(這里可以理解為調(diào)用者)是否是給出類(lèi)(這里可以理解為傳入的aClass參數(shù))或者其子類(lèi)的實(shí)例。

  • - (BOOL)isMemberOfClass:(Class)aClass;
    Returns a Boolean value that indicates whether the receiver is an instance of a given class.
    大意:判斷當(dāng)前接收者是否是給出類(lèi)的實(shí)例胯盯。

對(duì)于類(lèi)方法


  • 【1】懈费、[(id)[NSObject class] isKindOfClass:[NSObject class]]; 我們知道[NSObject class]會(huì)調(diào)用類(lèi)方法+class;我們查看源碼得知
- (Class)class {
    return object_getClass(self);
}
+ (Class)class {
    return self;
}

返回的是當(dāng)前類(lèi)自己

Note:其實(shí)在真正運(yùn)行的時(shí)候,llvm針對(duì)id類(lèi)型會(huì)做一層處理調(diào)用的真正的實(shí)現(xiàn)函數(shù)是如下邏輯,當(dāng)發(fā)現(xiàn)cls->hasCustomCore()false時(shí),直接返回當(dāng)前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));
}

所以當(dāng)我們調(diào)用實(shí)例方法類(lèi)方法 class時(shí)返回的都是當(dāng)前的類(lèi)對(duì)象;

接下到我們的isKindof這個(gè)實(shí)例方法;為什么是實(shí)例方法呢博脑?因?yàn)槲覀儗⑺鼜?qiáng)轉(zhuǎn)成了id類(lèi)型,所以我們看看源碼

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

Note:
1憎乙、我們?cè)谠创a文件NSObject.mm發(fā)現(xiàn)了+isKindOfClass的類(lèi)方法,這個(gè)NSObject類(lèi)方法是存貯在根元類(lèi)里面的, 在我們面試題中叉趣,其本質(zhì)是Class類(lèi)型泞边,只是強(qiáng)轉(zhuǎn)成了id類(lèi)型,當(dāng)調(diào)用isKindOfClass方法時(shí),其實(shí)是會(huì)去Class指向的元類(lèi)里面查找方法疗杉, 即會(huì)調(diào)用+isKindOfClass阵谚;不過(guò)在正式環(huán)境中我們是看不到這個(gè)類(lèi)方法的,僅能看到實(shí)例方法烟具。
2梢什、注意重點(diǎn):其實(shí)這個(gè)方法也是忽悠人的,經(jīng)過(guò)查看llvm源碼發(fā)現(xiàn)iskindOfclass經(jīng)過(guò)了優(yōu)化處理净赴,其真實(shí)會(huì)調(diào)用的方法是

// 源碼文件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);
}

很顯然當(dāng)?shù)谝粊?lái)的時(shí)候objNSObject類(lèi)對(duì)象,clstcls根元類(lèi),很明顯當(dāng)前是tcls == otherClass不成立绳矩,當(dāng)我們第二次來(lái)的時(shí)候tcls去取他的父類(lèi)賦值到tcls,根據(jù)isa走位圖,我們知道根元類(lèi)的父類(lèi)是NSObject的類(lèi)對(duì)象;所以此時(shí)tcls == otherClass 成立返回為true玖翅。

  • 【2】翼馆、[(id)[NSObject class] isMemberOfClass:[NSObject class]];對(duì)于isMemberOf這個(gè)方法源碼去看看;
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

這里就相當(dāng)簡(jiǎn)單了:直接判斷當(dāng)前的實(shí)例的類(lèi)對(duì)象是否等于傳入的cls
當(dāng)我們調(diào)用isMemberOfClass方法時(shí)金度,self就是[NSObject class]經(jīng)過(guò)強(qiáng)轉(zhuǎn)之后的id類(lèi)型(其本質(zhì)就是NSObject類(lèi)對(duì)象),當(dāng)經(jīng)過(guò)[self class]取class操作之后应媚,會(huì)變?yōu)?code>NSObject元類(lèi)對(duì)象和NSObject類(lèi)對(duì)象比較,所以為false猜极;

  • 【3】中姜、[(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 這里最終是LGPerson類(lèi)對(duì)象LGPerson元類(lèi)對(duì)象或者其父類(lèi)的比較,為false
  • 【4】 跟伏、[(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; 【3】都是false丢胚,這里還是它的子集,更加為false;
  • 【5】受扳、[(id)[NSObject alloc] isKindOfClass:[NSObject class]]; 這里簡(jiǎn)單分析true;
  • 【6】携龟、[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; 這里簡(jiǎn)單分析true;
  • 【7】、[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; 這里簡(jiǎn)單分析true;
  • 【8】勘高、[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; 這里簡(jiǎn)單分析true;

兩道面試題的總結(jié):

需要充分理解isa的走位圖:方法的存儲(chǔ)和類(lèi)型的繼承峡蟋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坟桅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕊蝗,更是在濱河造成了極大的恐慌仅乓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓬戚,死亡現(xiàn)場(chǎng)離奇詭異夸楣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)子漩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)裕偿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人痛单,你說(shuō)我怎么就攤上這事【⑼龋” “怎么了旭绒?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)焦人。 經(jīng)常有香客問(wèn)我挥吵,道長(zhǎng),這世上最難降的妖魔是什么花椭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任忽匈,我火速辦了婚禮,結(jié)果婚禮上矿辽,老公的妹妹穿的比我還像新娘丹允。我一直安慰自己,他們只是感情好袋倔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布雕蔽。 她就那樣靜靜地躺著,像睡著了一般宾娜。 火紅的嫁衣襯著肌膚如雪批狐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天前塔,我揣著相機(jī)與錄音嚣艇,去河邊找鬼。 笑死华弓,一個(gè)胖子當(dāng)著我的面吹牛食零,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播该抒,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼慌洪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼顶燕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起冈爹,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涌攻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后频伤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體恳谎,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年憋肖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了因痛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岸更,死狀恐怖鸵膏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怎炊,我是刑警寧澤谭企,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站评肆,受9級(jí)特大地震影響债查,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓜挽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一盹廷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧久橙,春花似錦俄占、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吭敢,卻和暖如春碰凶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鹿驼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工欲低, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畜晰。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓砾莱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親凄鼻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腊瑟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354