Runtime快捷筆記

方便自己查找

SEL

它是selector在 Objc 中的表示(Swift 中是 Selector 類)。selector 是方法選擇器,其實作用就和名字一樣,日常生活中,我們通過人名辨別誰是誰,==注意 Objc 在相同的類中不會有命名相同的兩個方法==。selector 對方法名進(jìn)行包裝,以便找到對應(yīng)的方法實現(xiàn)。
它的數(shù)據(jù)結(jié)構(gòu)是:

typedef struct objc_selector *SEL;

我們可以看出它是個映射到方法的 C 字符串,你可以通過 Objc 編譯器器命令@selector() 或者 Runtime 系統(tǒng)的 ==sel_registerName== 函數(shù)來獲取一個 SEL 類型的方法選擇器。

注意:
不同類中相同名字的方法所對應(yīng)的 selector 是相同的,由于變量的類型不同,所以不會導(dǎo)致它們調(diào)用方法實現(xiàn)混亂。

我們說明一下各個值的作用:

  • OBJC_ASSOCIATION_ASSIGN:表示弱引用關(guān)聯(lián),通常是基本數(shù)據(jù)類型,如int、float
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC:表示強(strong)引用關(guān)聯(lián)對象
  • OBJC_ASSOCIATION_COPY_NONATOMIC:表示關(guān)聯(lián)對象copy
  • OBJC_ASSOCIATION_RETAIN:表示強(strong)引用關(guān)聯(lián)對象,但不是線程安全的
  • OBJC_ASSOCIATION_COPY:表示關(guān)聯(lián)對象copy,但不是線程安全的

id

id 是一個參數(shù)類型,它是指向某個==類的實例==的指針。==objc_object==定義如下:

typedef struct objc_object *id;

struct objc_object {
    Class isa;
};

以上定義,看到 objc_object 結(jié)構(gòu)體包含一個 isa 指針,根據(jù) isa 指針就可以找到對象所屬的類。

注意:
isa 指針在代碼運行時并不總指向?qū)嵗龑ο笏鶎俚念愋?,所以不能依靠它來確定類型,要想確定類型還是需要用對象的 -class 方法。例如[NSString class];

Class

typedef struct objc_class *Class;

Class 其實是指向 ==objc_class== 結(jié)構(gòu)體的指針。objc_class 的數(shù)據(jù)結(jié)構(gòu)如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    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;

從 objc_class 可以看到,一個運行時類中關(guān)聯(lián)了它的父類指針、類名、成員變量、方法、緩存以及附屬的協(xié)議。

其中 ==objc_ivar_list== 和 ==objc_method_list== 分別是成員變量列表和方法列表:

// 成員變量列表
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

// 方法列表
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;
}

由此可見,我們可以動態(tài)修改 *methodList 的值來添加成員方法,這也是 Category 實現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因。

==objc_ivar_list 結(jié)構(gòu)體用來存儲成員變量的列表,而 objc_ivar 則是存儲了單個成員變量的信息;同理,objc_method_list 結(jié)構(gòu)體存儲著方法數(shù)組的列表,而單個方法的信息則由 objc_method 結(jié)構(gòu)體存儲。==

值得注意的時,objc_class 中也有一個 isa 指針,這說明 Objc 類本身也是一個對象。==為了處理類和對象的關(guān)系,Runtime 庫創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對象所屬的類就叫做元類。== Meta Class 表述了類對象本身所具備的元數(shù)據(jù)。

==我們所熟悉的類方法,就源自于 Meta Class。== 我們可以理解為類方法就是類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關(guān)的元類。

當(dāng)你發(fā)出一個類似[NSObject alloc] (類方法) 的消息時,實際上,這個消息被發(fā)送給了一個類對象(Class Object),這個類對象必須是一個元類的實例,而這個元類同時也是一個根元類(Root Meta Class)的實例。==所有元類的 isa 指針最終都指向根元類。==

所以當(dāng)[NSObject alloc] 這條消息發(fā)送給類對象的時候,運行時代碼 objc_msgSend() 會去它元類中查找能夠響應(yīng)消息的方法實現(xiàn),如果找到了,就會對這個類對象執(zhí)行方法調(diào)用。


image

上圖實現(xiàn)是 super_class 指針,虛線時 isa 指針。而根元類的父類是 NSObject,isa指向了自己。而 NSObject 沒有父類。

最后 objc_class 中還有一個 objc_cache ,緩存,它的作用很重要,后面會提到。

IMP

IMP在objc.h中的定義是:

typedef id (*IMP)(id, SEL, ...);

它就是一個函數(shù)指針,這是由編譯器生成的。當(dāng)你發(fā)起一個 ObjC 消息之后,最終它會執(zhí)行的那段代碼,就是由這個函數(shù)指針指定的。==而 IMP 這個函數(shù)指針就指向了這個方法的實現(xiàn)。==
如果得到了執(zhí)行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執(zhí)行方法,這在后面 Cache 中會提到。
你會發(fā)現(xiàn) IMP 指向的方法與 objc_msgSend 函數(shù)類型相同,參數(shù)都包含 id 和 SEL 類型。每個方法名都對應(yīng)一個 SEL 類型的方法選擇器,而每個實例對象中的 SEL 對應(yīng)的方法實現(xiàn)肯定是唯一的,通過一組 id和 SEL 參數(shù)就能確定唯一的方法實現(xiàn)地址。
而一個確定的方法也只有唯一的一組 id 和 SEL 參數(shù)。

消息

一些 Runtime 術(shù)語講完了,接下來就要說到消息了。體會蘋果官方文檔中的 messages aren’t bound to method implementations until Runtime。消息直到運行時才會與方法實現(xiàn)進(jìn)行綁定。

這里要清楚一點,objc_msgSend 方法看清來好像返回了數(shù)據(jù),其實objc_msgSend 從不返回數(shù)據(jù),而是你的方法在運行時實現(xiàn)被調(diào)用后才會返回數(shù)據(jù)。下面詳細(xì)敘述消息發(fā)送的步驟(如下圖):

image
  • 首先檢測這個 selector 是不是要忽略。比如 Mac OS X 開發(fā),有了垃圾回收就不理會 retain,release 這些函數(shù)。
  • 檢測這個 selector 的 target 是不是 nil,Objc 允許我們對一個 nil 對象執(zhí)行任何方法不會 Crash,因為運行時會被忽略掉。
  • 如果上面兩步都通過了,那么就開始查找這個類的實現(xiàn) IMP(方法實現(xiàn)的指針),先從 cache 里查找,如果找到了就運行對應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼。
  • 如果 cache 找不到就找類的方法列表中是否有對應(yīng)的方法。
  • 如果類的方法列表中找不到就到父類的方法列表中查找,一直找到 NSObject 類為止。
  • 如果還找不到,就要開始進(jìn)入動態(tài)方法解析了,后面會提到。

在消息的傳遞中,編譯器會根據(jù)情況在 objc_msgSend , objc_msgSend_stret , objc_msgSendSuper , objc_msgSendSuper_stret 這四個方法中選擇一個調(diào)用。如果消息是傳遞給父類,那么會調(diào)用名字帶有 Super 的函數(shù),如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時,會調(diào)用名字帶有 stret 的函數(shù)。

==++self 實在方法運行時被動態(tài)傳入的++==

當(dāng) objc_msgSend 找到方法對應(yīng)實現(xiàn)時,它將直接調(diào)用該方法實現(xiàn),并將消息中所有參數(shù)都傳遞給方法實現(xiàn),同時,它還將傳遞兩個隱藏參數(shù):

  • 接受消息的對象(self 所指向的內(nèi)容,當(dāng)前方法的對象指針)
  • 方法選擇器(_cmd 指向的內(nèi)容,當(dāng)前方法的 SEL 指針)
objc_msgSend(void /* id self, SEL op, ... */ )

因為在源代碼方法的定義中,我們并沒有發(fā)現(xiàn)這兩個參數(shù)的聲明。它們時在代碼被編譯時被插入方法實現(xiàn)中的。盡管這些參數(shù)沒有被明確聲明,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?/p>

這兩個參數(shù)中, self更實用。它是在方法實現(xiàn)中訪問消息接收者對象的實例變量的途徑。

其流程是這樣的:

  • 第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel實現(xiàn)方法,指定是否動態(tài)添加方法。若返回NO,則進(jìn)入下一步,若返回YES,則通過class_addMethod函數(shù)動態(tài)地添加方法,消息得到處理,此流程完畢。
  • 第二步:在第一步返回的是NO時,就會進(jìn)入- (id)forwardingTargetForSelector:(SEL)aSelector方法,這是運行時給我們的第二次機會,用于指定哪個對象響應(yīng)這個selector。不能指定為self。若返回nil,表示沒有響應(yīng)者,則會進(jìn)入第三步。若返回某個對象,則會調(diào)用該對象的方法。
  • 第三步:若第二步返回的是nil,則我們首先要通過- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法簽名,若返回nil,則表示不處理。若返回方法簽名,則會進(jìn)入下一步。
  • 第四步:當(dāng)?shù)谌椒祷胤椒ǚ椒ê灻?,就會調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我們可以通過anInvocation對象做很多處理,比如修改實現(xiàn)方法,修改響應(yīng)對象等
  • 第五步:若沒有實現(xiàn)- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么會進(jìn)入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我們沒有實現(xiàn)這個方法,那么就會crash,然后提示打不到響應(yīng)的方法。到此,動態(tài)解析的流程就結(jié)束了。

runtime Method

// 函數(shù)調(diào)用,但是不接收返回值類型為結(jié)構(gòu)體  
method_invoke  
// 函數(shù)調(diào)用,但是接收返回值類型為結(jié)構(gòu)體  
method_invoke_stret  
// 獲取函數(shù)名  
method_getName  
// 獲取函數(shù)實現(xiàn)IMP  
method_getImplementation  
// 獲取函數(shù)type encoding  
method_getTypeEncoding  
// 復(fù)制返回值類型  
method_copyReturnType  
// 復(fù)制參數(shù)類型  
method_copyArgumentType  
// 獲取返回值類型  
method_getReturnType  
// 獲取參數(shù)個數(shù)  
method_getNumberOfArguments  
// 獲取函數(shù)參數(shù)類型  
method_getArgumentType  
// 獲取函數(shù)描述  
method_getDescription  
// 設(shè)置函數(shù)實現(xiàn)IMP  
method_setImplementation  
// 交換函數(shù)的實現(xiàn)IMP  
method_exchangeImplementations  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容