iOS面試題之runtime

參考:

https://www.jianshu.com/p/9e975a1cab93

https://www.jianshu.com/p/6ebda3cd8052

https://blog.ibireme.com/2013/11/26/objective-c-messaging/

 

目錄

1、isa指針

2、消息發送

3、消息轉發

4、動態添加方法

5、給分類添加屬性

6、方法交換


 

本文不是講解,只做面試梳理,但本文可能比一些講解更易懂。

1、isa指針

//對象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

上面這個結構體就是我們常說的OC中的對象,OC中的所有對象(包括id)裏面有個isa,這個isa指向類對象,就是下面這個。

以NSString *str = @"123";爲例,str是一個對象,它的isa指向NSString這個類。

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                      OBJC2_UNAVAILABLE;  // 父類
    const char *name                       OBJC2_UNAVAILABLE;  // 類名
    long version                           OBJC2_UNAVAILABLE;  // 類的版本信息,默認爲0
    long info                              OBJC2_UNAVAILABLE;  // 類信息
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表
    struct objc_method_list **methodLists  OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協議鏈表
#endif

} OBJC2_UNAVAILABLE;

以上結構體就是類,它有isa,它是對象,稱爲類對象。類對象裏面的這些元素稱爲元數據。

類的isa指向meta-class(元類),super_class指向父類。

父類的isa指向它的元類,父類的super_class指向它的父類。

一直到NSObject,NSObject的isa指向NSObject的元類,NSObject的super_class指向nil。

NSObject的元類的isa指向自己,NSObject的元類的super_class指向NSObject。

 

2、消息發送

id objc_msgSend(id self, SEL _cmd, ...)

self是消息接收者,_cmd是selector(SEL)。

當向一般對象發送消息時,調用objc_msgSend;

當向super發送消息時,調用的是objc_msgSendSuper;

如果返回值是一個結構體,則會調用objc_msgSend_stret或objc_msgSendSuper_stret。

所謂消息發送,就是如何找到消息接收者的selector。

//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
//方法
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

以普通對象爲例,objc_msgSend(id self,  SEL _cmd)(看流程之前始終想着id self和 SEL _cmd這兩個參數)。

消息發送流程:

1、對象(id)通過它的isa找到class類對象(看第一小節中的objc_class結構體)

2、在class的cache緩存中查找(沒有緩存則初始化緩存),如果有,則根據_cmd這個方法名找到它的IMP實現,將Method加入緩存,並執行IMP。

3、如果沒有,從methodLists中查找,如果有,加入緩存,並執行IMP。

4、如果沒有,根據super_class指向,從父類中查找。

5、在父類中也是先從cache裏找,再從methodLists裏找,直到找到根類,如果一直都沒有則嘗試消息轉發(看第三節)。

 

3、消息轉發

消息轉發由_objc_msgForward完成。

消息轉發:(摘抄過來的)
1.調用resolveInstanceMethod:方法,允許用戶在此時爲該Class動態添加實現(看第四節)。如果有實現了,則調用並返回。如果仍沒實現,繼續下面的動作。

2.調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。如果獲取到,則直接轉發給它。如果返回了nil,繼續下面的動作。

3.調用methodSignatureForSelector:方法,嘗試獲得一個方法簽名。如果獲取不到,則直接調用doesNotRecognizeSelector拋出異常。如果獲取到了,調用forwardInvocation:方法,將獲取到的方法簽名包裝成Invocation傳入,如何處理就在這裏面了。

上面這3個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的。

我總結一下:

簡單來說,消息轉發有三個階段,對應三個方法:

resolveInstanceMethod:在這個方法裏可以動態添加一個方法實現。(看第四節)

forwardingTargetForSelector: 在這個方法裏可以返回一個對象,然後讓它去這個對象的類裏查找方法。(不明白的去看這個博客裏的"備用接收者"https://www.jianshu.com/p/6ebda3cd8052

methodSignatureForSelector: 在這個方法裏返回一個簽名,進入forwardInvocation:方法,然後再這個方法裏具體實現。

 

4、動態添加方法

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //執行foo函數
    [self performSelector:@selector(foo:)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(foo:)) {//如果是執行foo函數,就動態解析,指定新的IMP
        class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void fooMethod(id obj, SEL _cmd) {
    NSLog(@"Doing foo");//新的foo函數
}

 

5、給分類添加屬性

objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_COPY_NONATOMIC);

objc_getAssociatedObject(self,&key);

 

6、方法交換

Method test = class_getInstanceMethod(self, @selector(test));

Method otherTest = class_getInstanceMethod(self, @selector(otherTest));

method_exchangeImplementations(test, otherTest);

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章