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

從題目中得知re2、re4中都是指定類==當前類,所以都返回為NO
最后我們分析一下- (BOOL)isMemberOfClass:(Class)cls這個對象方法

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

從上述代碼中可以得出結論:如果當前對象的類是指定類則返回YES,否則返回NO

從題目中得知re6、re8中都是當前對象的類==指定類,所以都返回為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閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370