經典面試題

問題一:類存在幾份

  • 類的信息存在了類的isa的shiftcls區域中,而這樣的類信息只有一份,所以類對象只有一份

問題二:objc_object 與 對象的關係

  • 所有的對象 經由編譯會變成結構體objc_object
  • 所有的對象是來自NSObject (OC),但是真正到底層的是一個objc_object(C/C++)的結構體類型。
  • 在結構層面可以理解為上層OC與底層C/C++的對接:
    • 下層是通過結構體定義的模板,例如objc_class、objc_object
    • 上層是通過繼承NSObject類,而NSObject底層編譯後,繼承得是擁有isa屬性的objc_object

問題三:什麼是屬性?成員變量?實例變量?

  • 屬性(property):在OC中是通過@property開頭定義,且是帶下劃線成員變量 + setter + getter方法的變量
  • 成員變量(ivar):在OC的類中{}中定義的,且沒有下劃線的變量
  • 實例變量:通過當前對像類型,具備實例化的變量,是一種特殊的成員變量,例如 NSObject、UILabel、UIButton等

【附加】成员变量 和 实例变量什么区别?

  • 實例變量(即成員變量中的對像變量 就是 實例變量):以實例對象實例化來的,是一種特殊的成員變量
  • NSString常量類型, 因為不能添加屬性,如果定義在類中的{}中,是成員變量
  • 成員變量中 除去基本數據類型、NSString,其他都是 實例變量(即可以添加屬性成員變量),實例變量主要是判斷是不是對象

問題四:元類中為何會有類對象的類方法

  • 透過類的結構分析我們可以知道類方法放在元類內

isa與類的關係

  • 我們也可以利用兩個函數class_getClassMethod, class_getMethodImplementation 來打印出元類裡面是否有類方法。

準備

  • 先寫一個類LGPerson,聲明實現對象方法及類方法
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson

- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

  • main.m文件內部
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //創建一個person對象
        LGPerson *person = [LGPerson alloc];
        //獲取peron的類
        Class pClass     = object_getClass(person);
        //獲取類方法
        lgObjc_copyMethodList(pClass);
        //檢測類或元類是否有實例方法
        lgInstanceMethod_classToMetaclass(pClass);
        //檢測類或元類是否有類方法
        lgClassMethod_classToMetaclass(pClass);
        //獲取方法的實現地址
        lgIMP_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

  • lgObjc_copyMethodList 函数:用於獲取類的方法列表
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);
}

  • lgInstanceMethod_classToMetaclass 函数:獲取類的實例方法
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);
}

  • lgClassMethod_classToMetaclass 函数:用於獲取類的類方法
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);
}

  • lgIMP_classToMetaclass 函数:用於獲取方法的實現
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__);

  • 打印结果
Method, name: sayHello
lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148
2020-09-22 15:44:40.329896+0800 0x100001d00-0x7fff681f3580-0x7fff681f3580-0x100001d30
2020-09-22 15:44:40.330416+0800 lgIMP_classToMetaclass
2020-09-22 15:44:40.330502+0800 Hello, World!
Program ended with exit code: 0

探討打印結果

lgObjc_copyMethodList 函數分析

  • 這個自定義函數,目的為從傳入的類對象 看出方法列表內有哪些類方法,而從打印結果,可以看出這個類對象pclass的方法列表內部只有一個sayHappy類方法

lgInstanceMethod_classToMetaclass 函數分析

  • 首先我們先看看class_getInstanceMethod 是什麼

  • 官方文件-可以看到如果傳入的類或是父類沒有找到實例方法,則返回NULL

打印结果分析

  • 可以看到傳入pclassLGPerson類 ,而objc_getMetaClass 回傳的是元類LGPerson ,打印結果分別是:

Method method1 :地址0x1000031b0

  • 傳入的pclass 的類是LGPerson 內部有sayHello 這個實例方法,所以會打印出地址0x1000031b0

Method method2 :地址0x0

  • 傳入的metaClass 是元類LGPerson ,因為元類內部沒有sayHello這個實例方法,所以打印0x0

Method method3 :地址0x0

  • 傳入的pclass 的類是LGPerson ,內部沒有sayHappy 這個類方法,所以打印0x0

Method method4 :地址0x100003148

  • 傳入的metaClass 的元類是LGPerson ,內部有sayHappy 這個實例方法,所以會打印出地址

lgClassMethod_classToMetaclass 函數分析

  • 首先我們先看看class_getClassMethod 是什麼
  • 官方文件-可以看到如果傳入的類或是父類沒有找類方法,則返回NULL
_2020-09-22_10.19.23.png
//獲取類方法
//獲取一個類的類方法,相當於獲取元類的實例方法
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
//獲取元類
//判斷如果是元類,就直接返回,反之則繼續找ISA()
Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

  • class_getClassMethod的實現是獲取類的類方法,其本質就是獲取元類的實例方法,最終還是會走到class_getInstanceMethod,但是在這裡需要注意的一點是:在getMeta源碼中,如果判斷出cls是元類,那麼就不會再繼續往下遞歸查找,會直接返回this,其目的是為了防止元類的無限遞歸查找(如果沒有判斷元類返回this,就會一直透過ISA指向,會找到根元類,又在指向自己,引發無限循環)
  • 獲取類方法-源碼流程圖
_2020-09-23_10.17.16.png

打印结果分析

Method method1 :地址0x0

  • 傳入的pclass 的類是LGPerson ,@selector(sayHello)
  • 先判斷是否為元類,pclass 不是元類,返回LGPerson元類 然後在元類中查找sayHello 實例方法,查找順序元類 -> 根元類 -> 根類 -> nil,最后返回NULL,所以地址打印0x0

Method method2 :地址0x0

  • 傳入的metaClass 的類是LGPerson元類 ,@selector(sayHello)
  • 先判斷是否為元類,metaClass 是元類,返回LGPerson元類 然後在元類中查找sayHello 實例方法,元類沒有sayHello實例方法,所以地址打印0x0

Method method3 :地址0x100003148

  • 傳入的pclass 的類是LGPerson ,@selector(sayHappy)
  • 先判斷是否為元類,pclass 不是元類,返回LGPerson元類 然後在元類中查找sayHappy 實例方法,元類中有sayHappy 這個實例方法,直接返回這個實例方法。

Method method4 :地址0x100003148

  • 傳入的metaClass 的類是LGPerson元類 ,@selector(sayHappy)
  • 先判斷是否為元類,metaClass 是元類,返回LGPerson元類 然後在元類中查找sayHappy 實例方法,元類中有sayHappy 這個實例方法,直接返回這個實例方法。

class_getMethodImplementation函數分析

class_getMethodImplementation 主要是返回方法的具體實現,如下官方说明

  • 其大致含義就是:該函數在向類實例發送消息時會被調用,並返回一個指向方法實現函數的指針。這個函數會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數指針可能是一個指向runtime內部的函數,而不一定是方法的實際實現。如果類實例無法響應selector,則返回的函數指針將是運行時消息轉發機制的一部分。
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    //查找方法實現
    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    //如果没有找到,則進行消息轉發 
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

imp1 函數指針地址:0x100001d00

pClassLGPerson類,selsayHello 根據LGPerson文件,可以得出LGPerson類中可以查找到sayHello的具體實現,所以返回一個imp函數指針的地址

imp2 函數指針地址:0x7fff681f3580

pClassLGPerson元類,selsayHello 根據類方法存儲在元類中可知,sayHello是一個實例方法,並不存儲在元類中,也沒有其任何實現,所以進行了消息轉發

imp3 函數指針地址:0x7fff681f3580

pClassLGPerson類,selsayHappy 根據LGPerson文件,sayHappy是一個類方法,並不存儲在類中,也沒有其任何實現,所以進行了消息轉發

imp4 函數指針地址:0x100001d30

pClassLGPerson元類,selsayHappy 根據類方法存儲在元類文件,可以在元類中查找到sayHappy的具體實現,所以返回一個imp函數指針的地址

總結

  • class_getInstanceMethod:獲取實例方法,如果指定的類或其父類不包含帶有指定選擇器的實例方法,則為NULL
  • class_getClassMethod:獲取類方法,如果指定的類或其父類不包含具有指定選擇器的類方法,則為NULL。
  • class_getMethodImplementation:獲取方法的具體實現,如果未查找到,則進行消息轉發

問題五:iskindOfClass & isMemberOfClass 的理解


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

  • 打印結果

源碼解析

  • 先來看看isKindOfClassisMemberOfClass 源碼
  • isKindOfClass 源碼解析(實例方法 & 類方法)
//--isKindOfClass---类方法、对象方法
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
    // 获取类的元类 vs 传入类
    // 根元类 vs 传入类
    // 根类 vs 传入类
    // 举例:LGPerson vs 元类 (根元类) (NSObject)
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

//- isKindOfClass:第一次是获取对象类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
/*
获取对象的类 vs 传入的类 
父类 vs 传入的类
根类 vs 传入的类
nil vs 传入的类
*/
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

  • isMemberOfClass 源码解析(实例方法 & 类方法)
//-----类方法
//+ isMemberOfClass : 获取类的元类,与 传入类对比
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
//-----实例方法
//- isMemberOfClass : 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

源码分析总结

isKindOfClass

  • 類方法:元類(isa)-> 根元類(父類) -> 根類(父類)-> nil(父類) 與 傳入類的對比
  • 實例方法:對象的類 -> 父類 -> 根類 -> nil與傳入類的對比

isMemberOfClass

  • 類方法: 類的元類 與 傳入類 對比
  • 實例方法:對象的父類 與 傳入類 對比
  • 注意:isMemberOfClass類方法實例方法的流程是正常的,會走到上面分析的源碼,而isKindOfClass根本不會走到上面分析的源碼中(注意這裡,這是一個坑點!!!),而是會走到下面這個源碼中,其類方法實例方法都是走到objc_opt_isKindOfClass方法源碼中
  • 彙編調用如下
  • objc_opt_isKindOfClass方法源碼如下
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    //获取isa,
    //如果obj 是对象,则isa是类,
    //如果obj是类,则isa是元类
    Class cls = obj->getIsa(); 
    if (fastpath(!cls->hasCustomCore())) {
        // 如果obj 是对象,则在类的继承链进行对比,
        // 如果obj是类,则在元类的isa中进行对比
        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);
}

  • 為什麼會這樣呢?主要是因為在llvm中編譯時對其進行了優化處理
  • 所以調用objc_opt_isKindOfClass實際走的邏輯如圖所示

案例代碼執行結果分析

  • 根據源碼的分析,來分析代碼執行的結果為什麼為什麼是0或者1
_2020-09-23_4.22.17.png

使用類方法結果解析

  • re1 :1(相等) ,是NSObjectNSObject的對比,使用+isKindOfClass
    NSObject(傳入類,即根類)vs NSObject的元類即根元類--不相等
    NSObject(傳入類,即根類)vs 根元類的父類即根類
  • re2 :0(不相等),是NSObject與NSObject的對比,使用+isMemberOfClass
    NSObject 根類(傳入類) vs NSObject的元類即根元類
  • re3 :0(不相等) ,是LGPersonLGPerson的對比,使用+isKindOfClass
    LGPerson(傳入類) vs LGPerson的元類即元類LGPerson --不相等
    LGPerson(傳入類) vs元類LGPerson的父類即根元類 --不相等
    LGPerson(傳入類) vs根元類的父類即根類 --不相等
    LGPerson(傳入類) vs根類的父類即nil
  • re4 :0(不相等) ,是LGPersonLGPerson的對比,使用+isMemberOfClass
    LGPerson(傳入類) vs 元類

使用實例方法結果解析

  • re5 :1(相等) ,是NSObject對象與NSObject的對比,使用-isKindOfClass
    NSObject(傳入類,即根類) vs對象的isa即NSObject 根類-
  • re6 :1(相等) ,是NSObject對象與NSObject的對比,使用-isMemberOfClass
    NSObject(傳入類,即根類) vs 對象的類即NSObject根類
  • re7 :1(相等) ,是LGPerson對象與LGPerson的對比,使用-isKindOfClass
    LGPerson(傳入類) vs 對象的isa即LGPerson
  • re8 :1(相等),是LGPerson對象與LGPerson的對比,使用-isMemberOfClass
    LGPerson(傳入類) vs 對象的類即LGPerson
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。