iOS面試 - 方法歸屬&isKindOfClass

iOS開發(fā)底層探究之路

本篇文章將從幾個面試題出發(fā)坞淮,探究方法的歸屬以及isa與Superclass瓮恭。

objc_object 與對象的關系兰吟,objc_object 與 NSObject的關系

  • 所有對象在底層都是以objc_object為模版繼承來的踊兜。
  • 所有對象都是繼承自NSObject(根類)犹撒,而在底層中NSObject 是一個objc_objectC/C++)結構體跋选。
    所以結論:objc_object對象之間是繼承關系锁孟。

屬性裸卫、成員變量仿贬、實例變量

  • 屬性:帶下劃線的成員變量 + setter + getter,以@property 開頭定義的變量
  • 成員變量:定義在類.h文件的{}中的墓贿,不帶下劃線的變量
  • 實例變量:特殊的成員變量茧泪,經過實例化的成員變量對象,例如UIButton聋袋、UILabel
  • 成員變量中除去基本數(shù)據類型及{}中定義的NSString類型變量队伟,剩下的都是實例化后的實例變量,實例變量可理解為是擁有屬性對象幽勒。

方法的歸屬分析

下面通過一個例子來探究實例方法及類方法的歸屬問題:

#import <Foundation/Foundation.h>
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
#import "LGPerson.h"
@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

定義繼承于NSObject的LGPerson類嗜侮,并添加一個實例方法sayHello,一個類方法sayHappy啥容,下面通過幾個打印情況來分析:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 0x0000000100000000
        // LGTeacher *teacher = [LGTeacher alloc];

        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgObjc_copyMethodList(pClass);

        lgInstanceMethod_classToMetaclass(pClass);
        lgClassMethod_classToMetaclass(pClass);
        lgIMP_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

方法從上而下的定義如下面:

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

void lgObjc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //獲取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        LGLog(@"Method, name: %@", key);
    }
    free(methods);
}

void lgInstanceMethod_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(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    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));
    // 元類 為什么有 sayHappy 類方法 0 1
    //
    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);

    // - (void)sayHello;
    // + (void)sayHappy;
    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));

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

下面我們先給出打印結果棘钞,然后逐個分析:


方法歸屬結果打印

lgObjc_copyMethodList

object_getClass 獲取當前實例對象person類對象
此方法參數(shù)為Class類型的pClass,一個類對象干毅,class_copyMethodList 獲取當前類對象中的方法列表宜猜,遍歷此類對象所有的實例方法,根據打印情況只打印sayHello實例方法硝逢,可以驗證類對象里面存放實例方法姨拥,類方法不存放在類對象中绅喉。

lgInstanceMethod_classToMetaclass

方法中的metaClass為當前方法參數(shù)類對象pClass的元類,由objc_getMetaClass 根據類獲取到元類叫乌,class_getInstanceMethod 獲取實例方法的源碼分析后可知柴罐,在傳入類及傳入類的父類一級一級找下去直到找到返回,沒找到返回null憨奸,下面分別分析幾個打印情況:

  • method1:0x1000031b0

因為sayHello是實例方法革屠,當前pClass剛好也是存放sayHelloLGPerson 類,所以能找到方法并打印

  • method2:0x0

因為metaClass為元類排宰,元類LGPerson)--->根元類NSObject)--->NSObject--->nil,發(fā)現(xiàn)找不到當前的sayHello方法似芝,說明返回null,打印為0x0

  • method3:0x0

當前需要找方法sayHappy板甘,LGPerson)--->根類NSObject)--->nil,沒找到sayHappy方法党瓮,所以返回null,打印0x0

  • method4:0x100003148

尋找類方法sayHappy盐类,metaClass為當前類的元類寞奸,類方法存在類的元類中,所以此時在metaClass 中就找到sayHppy方法在跳,即可返回并打印0x100003148

lgClassMethod_classToMetaclass

跟上面一樣枪萄,方法參數(shù)pClass類對象,metaClass為當前類的元類猫妙,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();
    }

class_getClassMethod 源碼可見瓷翻,獲取類的類方法就是去獲取當前類的元類的實例方法getMeta()就是返回當前類的元類(如果getMeta()方法當前調用者為元類時吐咳,直接返回自己,不是的話就返回當前調用者的元類)元践,下面分別分析打印情況:

  • method1:0x0

sayhello實例方法韭脊,class_getClassMethod當前傳入為類,不是元類单旁,拿到元類沪羔,然后class_getInstanceMethod 方法尋找,元類 ---> 根元類--->NSObject ---> nil,沒找到sayHello象浑,返回null蔫饰,打印0x0

  • method2:0x0

class_getClassMethod當前傳入為metaClass元類,直接進行class_getInstanceMethod 方法查找當前元類--->根元類---> NSObject--->nil,沒找到實例方法sayHello方法愉豺,所以返回null,打印0x0

  • method3:0x100003148

當前查找方法為類方法sayHappy篓吁,在pClass類中尋找,所以class_getClassMethod 中要拿到元類蚪拦,然后進行查找杖剪,發(fā)現(xiàn)在元類中找到類方法sayHappy冻押,所以能打印方法地址0x100003148

  • method4: 0x100003148

class_getClassMethod 傳入即為類的元類,所以直接進行class_getInstanceMethod 方法查找盛嘿,在當前元類找到了sayHappy類方法洛巢,所以返回找到的方法

lgIMP_classToMetaclass

class_getMethodImplementation 返回方法的具體實現(xiàn):

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

如果在當前類對象里沒找到對應的方法實現(xiàn),就會觸發(fā)底層的消息轉發(fā)次兆,所以打印結果是四個都有值稿茉,而不是0x0

  • method1:0x100001d10

sayHello 實例方法,在當前pClass類中能找到當前方法芥炭,所以method1返回的是sayHello方法地址指針

  • method2:0x7fff6cd17580

metaClass 元類中尋找實例方法sayHello漓库,是找不到的,所以進行了底層的消息轉發(fā)蚤认,返回的不是sayHello的實現(xiàn)地址

  • method3:0x7fff6cd17580

sayHappy 類方法米苹,因為類方法是存儲在元類里的,所以在類pClass中找不到sayHappy方法實現(xiàn)地址砰琢,所以觸發(fā)了底層消息轉發(fā)

  • method4:0x100001d40

metaClass元類 中找到了sayHappy方法實現(xiàn)蘸嘶,所以返回的就是真正的sayHappy實現(xiàn)地址

isKindOfClass & isMemberOfClass

同樣,利用上面的類LGPerson進行下面探究:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        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(@" 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(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    }
    return 0;
}
isKind&isMember結果

首先理清一下isKindOfClass 方法及isMemberOfClass 方法:

  • isKindOfClass方法:
     + (BOOL)isKindOfClass:(Class)cls {
    // 類 vs 元類
    // 根元類 vs NSObject
    // NSObject vs NSObject
    // LGPerson vs 元類 (根元類) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO; }
    
    - (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
    }
    
    源碼所示陪汽,類方法+isKindOfClass 比較線路:元類 ---> 根元類 ---> NSObject ---> nil 與當前cls類比較训唱。實例方法-isKindOfClass比較路線:對象的類 ---> 父類 ---> 根類 ---> nilcls比較。

注意(有坑V吭?鲈觥!)


你以為就是這樣了训挡?當然不是澳骤,isKindOfClass 方法在llvm編譯器在編譯期做了編譯優(yōu)化,isKindOfClass 在底層走的方法是objc_opt_isKindOfClass 澜薄,打開源碼为肮,搜索objc_opt_isKindOfClass 打斷點調試,發(fā)現(xiàn)調用isKindOfClass類方法及實例方法肤京,走的都是這個方法:

// 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);
}

可見颊艳,無論是實例方法還是類方法調用,比較的對象鏈:元類 ---> 根元類 ---> NSObject ---> nil 忘分,也就是說isKindOfClass 就是遵循isa走向的規(guī)則


  • isMemberOfClass方法:
    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    
     - (BOOL)isMemberOfClass:(Class)cls {
         return [self class] == cls;
     }
    
    類方法+isMemberOfClass 判斷的是當前是否是元類棋枕,實例方法-isMemberOfClass 判斷是否是當前對象的類

類方法re1妒峦、re2重斑、re3re4
實例方法re5肯骇、re6绸狐、re7卤恳、re8

  • re1: 1

首先拿到前者NSObject 的元類(根元類),與后者NSObject類比較寒矿,不相等突琳,找根元類的父類,也就是NSObject類符相,比較結果為相等拆融,所以返回YES,打印1

  • re2: 0

NSObject 元類與NSObject類當然不相等,所以返回NO

  • re3: 0

LGPerson 類 與 LGPerson 元類不想等啊终,與 LGPerson 根元類不想等镜豹,與 NSObject類不想等,與 nil不想等,所以返回NO

  • re4: 0

LGPerson 類與LGPerson 元類不相等,返回NO

  • re5: 1

NSObject實例對象的類NSObject 當然與NSObject 類相等蓝牲,返回YES

  • re6: 1

NSObject實例對象的類NSObject 當然與NSObject 類相等

  • re7: 1

LGPerson 實例對象的類LGPersonLGPerson 類相等

  • re8: 1

LGPerson 實例對象的類LGPersonLGPerson 類相等

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末趟脂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子例衍,更是在濱河造成了極大的恐慌昔期,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件佛玄,死亡現(xiàn)場離奇詭異硼一,居然都是意外死亡,警方通過查閱死者的電腦和手機梦抢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門般贼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人奥吩,你說我怎么就攤上這事哼蛆。” “怎么了霞赫?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵腮介,是天一觀的道長。 經常有香客問我绩脆,道長萤厅,這世上最難降的妖魔是什么橄抹? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任靴迫,我火速辦了婚禮,結果婚禮上楼誓,老公的妹妹穿的比我還像新娘玉锌。我一直安慰自己,他們只是感情好疟羹,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布主守。 她就那樣靜靜地躺著禀倔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪参淫。 梳的紋絲不亂的頭發(fā)上救湖,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機與錄音涎才,去河邊找鬼鞋既。 笑死,一個胖子當著我的面吹牛耍铜,可吹牛的內容都是我干的邑闺。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼棕兼,長吁一口氣:“原來是場噩夢啊……” “哼陡舅!你這毒婦竟也來了?” 一聲冷哼從身側響起伴挚,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤靶衍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后章鲤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摊灭,經...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年败徊,在試婚紗的時候發(fā)現(xiàn)自己被綠了帚呼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡皱蹦,死狀恐怖煤杀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情沪哺,我是刑警寧澤沈自,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站辜妓,受9級特大地震影響枯途,放射性物質發(fā)生泄漏。R本人自食惡果不足惜籍滴,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一酪夷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孽惰,春花似錦晚岭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽库说。三九已至,卻和暖如春片择,著一層夾襖步出監(jiān)牢的瞬間潜的,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工字管, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夏块,地道東北人。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓纤掸,卻偏偏與公主長得像脐供,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子借跪,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361