iOS開發(fā)經(jīng)驗(yàn)(14)-runtime

目錄

  1. Objective-C Runtime到底是什么
  2. Objective-C的元素認(rèn)知
  3. Runtime詳解
  4. 應(yīng)用場景
  5. Runtime缺點(diǎn)及Runtime常用函數(shù)

引用:
Objective-C Runtime 1小時(shí)入門教程

一、Objective-C Runtime到底是什么

我們將C++和Objective進(jìn)行對(duì)比,雖然C++和Objective-C都是在C的基礎(chǔ)上加入面向?qū)ο蟮奶匦詳U(kuò)充而成的程序設(shè)計(jì)語言,但二者實(shí)現(xiàn)的機(jī)制差異很大。C++是基于靜態(tài)類型,而Objective-C是基于動(dòng)態(tài)運(yùn)行時(shí)類型。也就是說用C++編寫的程序通過編譯器直接把函數(shù)地址硬編碼進(jìn)入可執(zhí)行文件;而Objective-C無法通過編譯器直接把函數(shù)地址硬編碼進(jìn)入可執(zhí)行文件,而是在程序運(yùn)行的時(shí)候,利用Runtime根據(jù)條件判斷作出決定。函數(shù)標(biāo)識(shí)與函數(shù)過程的真正內(nèi)容之間的關(guān)聯(lián)可以動(dòng)態(tài)修改。Runtime是Objective不可缺少的重要一部分。

程序執(zhí)行過程:預(yù)處理->編譯->鏈接->運(yùn)行。

  • Objective-C 是面相運(yùn)行時(shí)的語言,就是說它會(huì)盡可能的把編譯和鏈接時(shí)要執(zhí)行的邏輯延遲到運(yùn)行時(shí)。
  • OC之所以從C變成了面向?qū)ο蟮腃,擁有動(dòng)態(tài)特性,都是由于運(yùn)行時(shí)系統(tǒng)的存在。
  • Objective-C動(dòng)態(tài)運(yùn)行庫會(huì)自動(dòng)注冊(cè)我們代碼中定義的所有的類。我們也可以在運(yùn)行時(shí)創(chuàng)建類定義并使用objc_addClass函數(shù)來注冊(cè)它們。

二、Objective-C的元素認(rèn)知

2.1 對(duì)象(id)

typedef struct objc_class *Class;

struct objc_object {
    Class isa;
}
typedef struct objc_object *id;

Objective-C 中的對(duì)象的定義 struct objc_object,Objective-C 中的對(duì)象本質(zhì)上是結(jié)構(gòu)體,它是struct objc_object 類型的指針,objc_object被源碼typedef成了id類型,這也是為什么 id 類型可以指向任意對(duì)象的原因,其中 isa 是它唯一的私有成員變量。這個(gè)對(duì)象的 isa指針指向它所屬的類。

2.2 類(Class)

Objective-C 中的類的定義 struct objc_class 。同樣的,Objective-C 中類也是一個(gè)結(jié)構(gòu)體。所以,Objective-C 中的類本質(zhì)上也是對(duì)象,我們稱之為類對(duì)象。
objc_class源碼如下:

typedef struct objc_class *Class;
struct objc_class { 
 Class isa                                 OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__
 Class super_class                         OBJC2_UNAVAILABLE; // 父類
 const char *name                          OBJC2_UNAVAILABLE; // 類名
 long version                              OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0,可以通過runtime函數(shù)class_setVersion或者class_getVersion進(jìn)行修改、讀取
 long info                                 OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行時(shí)期使用的一些位標(biāo)識(shí),如CLS_CLASS (0x1L) 表示該類為普通 class,其中包含實(shí)例方法和變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
 long instance_size                        OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大?。ò◤母割惱^承下來的實(shí)例變量)
 struct objc_ivar_list *ivars              OBJC2_UNAVAILABLE; // 該類的成員變量地址列表
 struct objc_method_list **methodLists     OBJC2_UNAVAILABLE; // 方法地址列表,與 info 的一些標(biāo)志位有關(guān),如CLS_CLASS (0x1L),則存儲(chǔ)實(shí)例方法,如CLS_META (0x2L),則存儲(chǔ)類方法;
 struct objc_cache *cache                  OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址,用于提升效率;
 struct objc_protocol_list *protocols      OBJC2_UNAVAILABLE; // 存儲(chǔ)該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

由以上代碼可見:

Class是一個(gè)指向objc_class結(jié)構(gòu)體的指針,而id是一個(gè)指向objc_object結(jié)構(gòu)體的指針,其中的isa是一個(gè)指向objc_class結(jié)構(gòu)體的指針。其中的id就是我們所說的對(duì)象,Class就是我們所說的類。

類與對(duì)象的區(qū)別就是類比對(duì)象多了很多特征成員,類也可以當(dāng)做一個(gè)objc_object來對(duì)待,也就是說類和對(duì)象都是對(duì)象,分別稱作類對(duì)象(class object)和實(shí)例對(duì)象(instance object),這樣我們就可以區(qū)別對(duì)象和類了。

isa:

在OC中,除了NSProxy類以外,所有的類都是NSObject的子類。在Foundation框架下,NSObject和NSProxy是兩個(gè)基類。id是一個(gè)指向 objc_object 結(jié)構(gòu)體的指針,該結(jié)構(gòu)體只有一個(gè)成員isa,所以任何繼承自 NSObject 的類對(duì)象都可以用 id 來指代。

從上述兩個(gè)代碼塊內(nèi),object和class里面分別都包含一個(gè)isa:

  1. objc_object(實(shí)例對(duì)象)中isa指針指向的類結(jié)構(gòu)稱為class(也就是該對(duì)象所屬的類),其中存放著普通成員變量與動(dòng)態(tài)方法(“-”開頭的方法)。對(duì)象的實(shí)例方法調(diào)用時(shí),通過對(duì)象的 isa 在類中獲取方法的實(shí)現(xiàn);
  2. objc_class中isa指針指向的類結(jié)構(gòu)稱為metaclass,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)。類對(duì)象的類方法調(diào)用時(shí),通過類的 isa 在元類中獲取方法的實(shí)現(xiàn);

super_class:

指向該類的父類的指針,如果該類是根類(如NSObject或NSProxy),那么super_class就為nil。

元類(metaClass):

所有的metaclass中isa指針都是指向根metaclass,而根metaclass則指向自身。根metaclass是通過繼承根類產(chǎn)生的,與根class結(jié)構(gòu)體成員一致,不同的是根metaclass的isa指針指向自身。

  • 類也是對(duì)象,也稱類對(duì)象,類是元類的實(shí)例,因此,我們也可以通過調(diào)用類方法,比如 [NSObject new],給類對(duì)象發(fā)送消息。同樣的,類對(duì)象能否響應(yīng)這個(gè)消息也要通過 isa 找到類對(duì)象所屬的類(元類)才能知道。也就是說,實(shí)例方法是保存在類中的,而類方法是保存在元類中的。
  • 元類也是對(duì)象(元類對(duì)象),元類也是某個(gè)類的實(shí)例,這個(gè)類我們稱之為根元類(root metaclass)。元類的isa指向NSObject(NSObject:根元類,其isa指向自己,其super_class也指向自己);
  • 存放的是靜態(tài)成員變量和類方法;沒有實(shí)例方法。

2.3 方法(Method)

2.3.1 SEL

SEL是selector在Objective-C中的表示類型, 表示方法的名字/簽名,selector可以理解為區(qū)別方法的ID。

typedef struct objc_selector *SEL;

objc_selector的定義如下:

struct objc_selector {
    char *name;                       OBJC2_UNAVAILABLE;// 名稱
    char *types;                      OBJC2_UNAVAILABLE;// 存儲(chǔ)著方法的參數(shù)類型和返回值類型。
};

name和types都是char類型。

typedef struct objc_selector *SEL; 

2.3.2 IMP

終于到IMP了,它在objc.h中得定義如下:

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

IMP是“implementation”的縮寫,它是由編譯器生成的一個(gè)函數(shù)指針。當(dāng)你發(fā)起一個(gè)消息后(下文介紹),這個(gè)函數(shù)指針決定了最終執(zhí)行哪段代碼。

2.3.3 Method

方法鏈表里面存儲(chǔ)的是Method 類型,Method代表類中的某個(gè)方法的類型:Method = IMP + SEL + types,Method也是一個(gè)結(jié)構(gòu)體對(duì)象。
Method 在頭文件 objc_class.h中定義如下:

typedef struct objc_method *Method;

objc_method的定義如下:

struct objc_method {
    SEL method_name                   OBJC2_UNAVAILABLE; // 方法名(SEL、_cmd)
    char *method_types                OBJC2_UNAVAILABLE; // 方法返回值和參數(shù)的類型
    IMP method_imp                    OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn),指向該方法的具體實(shí)現(xiàn)的函數(shù)指針
}

Method由三個(gè)部分組成:

  1. 方法名method_name類型為SEL,Selector相當(dāng)于一個(gè)方法的id;
  2. 方法類型method_types是一個(gè)char指針,存儲(chǔ)著方法的參數(shù)類型和返回值類型;
  3. 方法實(shí)現(xiàn)method_imp的類型為IMP,IMP是方法的實(shí)現(xiàn);

這樣分開的一個(gè)便利之處是selector和IMP之間的對(duì)應(yīng)關(guān)系可以被改變。比如一個(gè) IMP 可以有多個(gè) selectors 指向它。

注:

  1. 實(shí)例方法在對(duì)象的class中找,而類方法在對(duì)象所屬的類的的metaClass中找。
  2. OC中的方法實(shí)質(zhì)上是一個(gè)有id self和 SEL _cmd兩個(gè)參數(shù)的C方法。

2.4 Ivar

Ivar代表類中實(shí)例變量的類型

typedef struct objc_ivar *Ivar;

objc_ivar的定義如下:

struct objc_ivar {
    char *ivar_name                   OBJC2_UNAVAILABLE; // 變量名
    char *ivar_type                   OBJC2_UNAVAILABLE; // 變量類型
    int ivar_offset                   OBJC2_UNAVAILABLE; // 基地址偏移字節(jié)
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE; // 占用空間
#endif
}

2.5 objc_property_t

objc_property_t是屬性,它的定義如下:

typedef struct objc_property *objc_property_t;

objc_property是內(nèi)置的類型,與之關(guān)聯(lián)的還有一個(gè)objc_property_attribute_t,它是屬性的attribute,也就是其實(shí)是對(duì)屬性的詳細(xì)描述,包括屬性名稱、屬性編碼類型、原子類型/非原子類型等。它的定義如下:

typedef struct {
    const char *name; // 名稱
    const char *value;  // 值(通常是空的)
} objc_property_attribute_t;

2.6 Cache

Catch的定義如下:

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache {
    unsigned int mask                   OBJC2_UNAVAILABLE;
    unsigned int occupied               OBJC2_UNAVAILABLE;
    Method buckets[1]                   OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的總數(shù)。在方法查找中,Runtime使用這個(gè)字段確定數(shù)組的索引位置。
occupied: 實(shí)際占用cache buckets的總數(shù)。
buckets: 指定Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長。
objc_msgSend(下文講解)每調(diào)用一次方法后,就會(huì)把該方法緩存到cache列表中,下次的時(shí)候,就直接優(yōu)先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。

2.7 Catagory

這個(gè)就是我們平時(shí)所說的類別了,很熟悉吧。它可以動(dòng)態(tài)的為已存在的類添加新的方法。
它的定義如下:

typedef struct objc_category *Category;
objc_category的定義如下:

struct objc_category {
    char *category_name                           OBJC2_UNAVAILABLE; // 類別名稱
    char *class_name                              OBJC2_UNAVAILABLE; // 類名
    struct objc_method_list *instance_methods     OBJC2_UNAVAILABLE; // 實(shí)例方法列表
    struct objc_method_list *class_methods        OBJC2_UNAVAILABLE; // 類方法列表
    struct objc_protocol_list *protocols          OBJC2_UNAVAILABLE; // 協(xié)議列表
}
2. OC的動(dòng)態(tài)特性**

Objective-C具有相當(dāng)多的動(dòng)態(tài)特性,這個(gè)動(dòng)態(tài)體現(xiàn)在三個(gè)方面:動(dòng)態(tài)類型(Dynamic typing),動(dòng)態(tài)綁定(Dynamic binding)和動(dòng)態(tài)加載(Dynamic loading)。

1. 動(dòng)態(tài)類型
即運(yùn)行時(shí)再?zèng)Q定對(duì)象的類型。這類動(dòng)態(tài)特性在日常應(yīng)用中非常常見,簡單說就是id類型。id類型即通用的對(duì)象類,任何對(duì)象都可以被id指針?biāo)?。其類型需要等到運(yùn)行時(shí)才能決定,在編譯時(shí)id就是一個(gè)通用類型。

2. 動(dòng)態(tài)綁定

動(dòng)態(tài)綁定概念:基于動(dòng)態(tài)類型,在某個(gè)實(shí)例對(duì)象被確定后,其類型便被確定了。該對(duì)象對(duì)應(yīng)的屬性和響應(yīng)的消息也被完全確定,這就是動(dòng)態(tài)綁定。在繼續(xù)之前,需要明確Objective-C中消息的概念。由于OC的動(dòng)態(tài)特性,在OC中其實(shí)很少提及“函數(shù)”的概念,傳統(tǒng)的函數(shù)一般在編譯時(shí)就已經(jīng)把參數(shù)信息和函數(shù)實(shí)現(xiàn)打包到編譯后的源碼中了,****而在OC中最常使用的是消息機(jī)制。調(diào)用一個(gè)實(shí)例的方法,所做的是向該實(shí)例的指針發(fā)送消息,實(shí)例在收到消息后,從自身的實(shí)現(xiàn)中尋找響應(yīng)這條消息的方法。

動(dòng)態(tài)綁定作用:即是在實(shí)例所屬類確定后,將某些屬性和相應(yīng)的方法綁定到實(shí)例上。這里所指的屬性和方法當(dāng)然包括了原來沒有在類中實(shí)現(xiàn)的,而是在運(yùn)行時(shí)才需要的新加入的實(shí)現(xiàn)。
在Cocoa層,我們一般向一個(gè)NSObject對(duì)象發(fā)送-respondsToSelector:或者-instancesRespondToSelector:等來確定對(duì)象是否可以對(duì)某個(gè)SEL做出響應(yīng),而在OC消息轉(zhuǎn)發(fā)機(jī)制被觸發(fā)之前,對(duì)應(yīng)的類的+resolveClassMethod:+resolveInstanceMethod:將會(huì)被調(diào)用,在此時(shí)有機(jī)會(huì)動(dòng)態(tài)地向類或者實(shí)例添加新的方法,也即類的實(shí)現(xiàn)是可以動(dòng)態(tài)綁定的。

//該方法在OC消息轉(zhuǎn)發(fā)生效前被調(diào)用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{ 
    if (aSEL == @selector(resolveThisMethodDynamically)) {
        //向[self class]中新加入返回為void的實(shí)現(xiàn),SEL名字為aSEL,實(shí)現(xiàn)的具體內(nèi)容為dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
        return YES;
    }
    return [super resolveInstanceMethod:aSel];
}  

3. 動(dòng)態(tài)加載
根據(jù)需求加載所需要的資源,這點(diǎn)很容易理解,對(duì)于iOS開發(fā)來說,基本就是根據(jù)不同的機(jī)型做適配。最經(jīng)典的例子就是在Retina設(shè)備上加載@2x的圖片,而在老一些的普通屏設(shè)備上加載原圖。隨著Retina iPad的推出,和之后可能的Retina Mac的出現(xiàn),這個(gè)特性相信會(huì)被越來越多地使用。

基本的動(dòng)態(tài)特性在常規(guī)的Cocoa開發(fā)中非常常用,特別是動(dòng)態(tài)類型和動(dòng)態(tài)綁定。以下主要結(jié)合Runtime原理深入運(yùn)行時(shí)特性。

3. RunTime基本概念

RunTime簡稱運(yùn)行時(shí),是一套底層的 C 語言 API,OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制,主要特征就是動(dòng)態(tài)綁定,消息轉(zhuǎn)發(fā)。

C與OC區(qū)別:

  • 在編譯階段,C語言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò),函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù);
  • 在編譯階段,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過就不會(huì)報(bào)錯(cuò)。對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來調(diào)用。

Objective-C 是一個(gè)動(dòng)態(tài)語言,這意味著它不僅需要一個(gè)編譯器,也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來動(dòng)態(tài)得創(chuàng)建類和對(duì)象、進(jìn)行消息傳遞和轉(zhuǎn)發(fā)。

開發(fā)者在編碼過程中,可以給任意一個(gè)對(duì)象發(fā)送消息,在編譯階段只是確定了要向接收者發(fā)送這條消息,而接受者將要如何響應(yīng)和處理這條消息,那就要看運(yùn)行時(shí)來決定了。你向一個(gè)對(duì)象發(fā)送消息并不意味著它會(huì)執(zhí)行它。Object(對(duì)象)會(huì)檢查消息的發(fā)送者,基于這點(diǎn)再?zèng)Q定是執(zhí)行一個(gè)不同的方法還是轉(zhuǎn)發(fā)消息到另一個(gè)目標(biāo)對(duì)象上。

一般情況開發(fā)者只需要編寫 OC 代碼即可,Runtime 系統(tǒng)自動(dòng)在幕后把我們寫的源代碼在編譯階段轉(zhuǎn)換成運(yùn)行時(shí)代碼,在運(yùn)行時(shí)確定對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)和調(diào)用具體哪個(gè)方法。消息直到運(yùn)行時(shí)才綁定到方法實(shí)現(xiàn)上。

Runtime中方法的動(dòng)態(tài)綁定讓我們寫代碼時(shí)更具靈活性,如我們可以把消息轉(zhuǎn)發(fā)給我們想要的對(duì)象,或者隨意交換一個(gè)方法的實(shí)現(xiàn)等。

1. objc_msgSend函數(shù)簡介
當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí),就會(huì)走正常的方法調(diào)用流程。但如果一個(gè)對(duì)象無法接收指定消息時(shí),又會(huì)發(fā)生什么事呢?默認(rèn)情況下,如果是以 [object message]的方式調(diào)用方法,如果object無法響應(yīng)message消息時(shí),編譯器會(huì)報(bào)錯(cuò)。但如果是以perform…的形式來調(diào)用,則需要等到運(yùn) 行時(shí)才能確定object是否能接收message消息。如果不能,則程序崩潰。

通常,當(dāng)我們不能確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:來判斷一下。不過,我們這邊想討論下不使用respondsToSelector:判斷的情況。

當(dāng)一個(gè)對(duì)象無法接收某一消息時(shí),就會(huì)啟動(dòng)所謂”消息轉(zhuǎn)發(fā)(message forwarding)“機(jī)制,通過這一機(jī)制,我們可以告訴對(duì)象如何處理未知的消息。默認(rèn)情況下,對(duì)象接收到未知的消息,會(huì)導(dǎo)致程序崩潰
最初接觸到OC Runtime,一定是從[receiver message]這里開始的。[receiver message]會(huì)被編譯器轉(zhuǎn)化為:

id objc_msgSend ( id self, SEL op, ... );

這是一個(gè)可變參數(shù)函數(shù)。第二個(gè)參數(shù)類型是SEL。SEL在OC中是selector方法選擇器。

typedef struct objc_selector *SEL;

objc_selector是一個(gè)映射到方法的C字符串。需要注意的是@selector()選擇只與函數(shù)名有關(guān)。

在receiver拿到對(duì)應(yīng)的selector之后,如果自己無法執(zhí)行這個(gè)方法,那么該條消息要被轉(zhuǎn)發(fā)?;蛘吲R時(shí)動(dòng)態(tài)的添加方法實(shí)現(xiàn)。如果轉(zhuǎn)發(fā)到最后依舊沒法處理,程序就會(huì)崩潰。

所以編譯期僅僅是確定了要發(fā)送消息,而消息如何處理是要運(yùn)行期需要解決的事情。

objc_msgSend函數(shù)究竟會(huì)干什么事情呢?
2. 消息發(fā)送Messaging階段
當(dāng)我們創(chuàng)建一個(gè)新對(duì)象時(shí),先為其分配內(nèi)存,并初始化其成員變量。其中isa指針也會(huì)被初始化,讓對(duì)象可以訪問類及類的繼承體系。
總結(jié)一下objc_msgSend會(huì)做一下幾件事情:

  1. 檢查target是不是為nil。
  • 如果這里有相應(yīng)的nil的處理函數(shù),就跳轉(zhuǎn)到相應(yīng)的函數(shù)中。
  • 如果沒有處理nil的函數(shù),就自動(dòng)清理現(xiàn)場并返回。這一點(diǎn)就是為何在OC中給nil發(fā)送消息不會(huì)崩潰的原因。
  1. 檢測(cè)這個(gè) selector是不是要忽略的。
  2. 確定不是給nil發(fā)消息之后,objc_msgSend通過對(duì)象的isa指針獲取到類的結(jié)構(gòu)體,在該class的緩存中查找方法對(duì)應(yīng)的IMP實(shí)現(xiàn)。如果找到,就跳轉(zhuǎn)進(jìn)去執(zhí)行。如果沒有找到,就在方法分發(fā)表里面繼續(xù)查找。如果以上嘗試都失敗了,接下來就會(huì)循環(huán)嘗試父類的緩存和方法列表,一直找到NSObject為止(因?yàn)镹SObject的superclass為nil(還是它自己?),才跳出循環(huán))。一旦定位到selector,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn),并傳入相應(yīng)的參數(shù)來執(zhí)行方法的具體實(shí)現(xiàn);如果最后沒有定位到selector,則會(huì)走消息轉(zhuǎn)發(fā)流程

注:為了加速消息的處理,運(yùn)行時(shí)系統(tǒng)緩存使用過的selector及對(duì)應(yīng)的方法的地址。

至此,發(fā)送消息Messaging階段完成。這一階段主要完成的是通過select()快速查找IMP的過程。

默認(rèn)情況下,如果是以 [object message]的方式調(diào)用方法,如果object無法響應(yīng)message消息時(shí),編譯器會(huì)報(bào)錯(cuò)。但如果是以perform…的形式來調(diào)用,則需要等到運(yùn) 行時(shí)才能確定object是否能接收message消息。如果不能,則程序崩潰。

通常,當(dāng)我們不能確定一個(gè)對(duì)象是否能接收某個(gè)消息時(shí),會(huì)先調(diào)用respondsToSelector:來判斷一下。如下代碼所示:

if ([self respondsToSelector:@selector(method)]) {
    [self performSelector:@selector(method)];
}

不過,我們這邊想討論下不使用respondsToSelector:判斷的情況。

上面提到的,拋出判斷和一般發(fā)送消息的方法:當(dāng)消息傳遞過程中找不到對(duì)應(yīng)的方法時(shí),會(huì)拋出unrecognzed selector send to instace ...的錯(cuò)誤,即找不到指定的方法,在此之前可以在三個(gè)方法中實(shí)現(xiàn)補(bǔ)救。

3. 動(dòng)態(tài)綁定階段
動(dòng)態(tài)綁定,從名稱來看就大致懂了。如果調(diào)用一個(gè)類的方法,而這個(gè)類及其父類均沒有實(shí)現(xiàn)這個(gè)方法。那么我們就在運(yùn)行時(shí)綁定此方法到該類。注意我們可以在這里動(dòng)態(tài)增加方法實(shí)現(xiàn),不過這種方案更多的是為了實(shí)現(xiàn)@dynamic屬性。

resolveInstanceMethod動(dòng)態(tài)方法解析:

  • 將未能識(shí)別的消息動(dòng)態(tài)添加到接收者的類中,resolveInstanceMethod方法返回的是一個(gè)BOOL類型的值,用于判斷是否接收這消息;
  • 這個(gè)函數(shù)首先判斷是否是meta-class類,如果不是元類,就執(zhí)行_class_resolveInstanceMethod,如果是元類,執(zhí)行_class_resolveClassMethod。

對(duì)象在接收到未知的消息時(shí),首先會(huì)調(diào)用所屬類的類方法-resolveInstanceMethod:(實(shí)例方法)或 者+resolveClassMethod:(類方法)。
在這個(gè)方法中,我們有機(jī)會(huì)為該未知消息新增一個(gè)”處理方法”“。不過使用該方法的前提是我們已經(jīng) 實(shí)現(xiàn)了該”處理方法”,只需要在運(yùn)行時(shí)通過class_addMethod函數(shù)動(dòng)態(tài)添加到類里面就可以了。如下代碼所示:

#import <Foundation/Foundation.h>
@interface Father : NSObject
@end

#import "Father.h"
#import "Son.h"
@implementation Father

- (void)son {
    Son *s = [[Son alloc] init];
    // 默認(rèn)Son,沒有實(shí)現(xiàn)run方法,可以通過performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)。
    // 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
    [s performSelector:@selector(run)];
}

@end

son:

#import <Foundation/Foundation.h>
@interface Son : NSObject
@end


#import "Son.h"
#import <objc/runtime.h>
@implementation Son
// void(*)()
// 默認(rèn)方法都有兩個(gè)隱式參數(shù),
void testRun(id self,SEL sel) {
    [Son.new  eat];
    NSLog(@"son is runing");
}

- (void)eat {
    NSLog(@"son is eating");
}
// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過來.
// 剛好可以用來判斷,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    //判斷方法是否是run
    if ([NSStringFromSelector(sel) isEqualToString:@"run"]) {
        // 動(dòng)態(tài)添加run方法

        // 第一個(gè)參數(shù):給哪個(gè)類添加方法
        // 第二個(gè)參數(shù):添加方法的方法編號(hào)
        // 第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
        // 第四個(gè)參數(shù):函數(shù)的類型,(返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmd
        class_addMethod([self class], sel, (IMP)testRun, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end

打印輸出:
son is eating
son is runing

@selector(run)被動(dòng)態(tài)添加到了Son的類方法列表中。

如果也沒有找到IMP的實(shí)現(xiàn),resloveInstanceMethod:返回NO之后,就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段。

4. 消息轉(zhuǎn)發(fā)Message Forwarding階段
如果在上一步無法處理消息,則Runtime會(huì)繼續(xù)調(diào)以下方法:
-forwardindTargetWithSelctor:方法。在這個(gè)方法中,返回的對(duì)象就是message的接收者,然后會(huì)回到resloveInstanceMethod方法,從新開始消息轉(zhuǎn)發(fā)過程,如果返回nil則會(huì)進(jìn)入下一個(gè)方法中(-forwardInvocation)去判斷是否響應(yīng)這個(gè)消息。

消息轉(zhuǎn)發(fā)機(jī)制基本上分為兩個(gè)步驟:
1. 備用接收者 forwardingTargetForSelector

  • 如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者,且消息會(huì)被分發(fā)到這個(gè)對(duì)象。當(dāng)然這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無限循環(huán)。
  • 可借這個(gè)對(duì)象來處理消息并返回,這樣在對(duì)象外部看來,還是由該對(duì)象親自處理了這一消息。
  • 這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上。但這一步無法對(duì)消息進(jìn)行處理,如操作消息的參數(shù)和返回值。

當(dāng)前的SEL無法找到相應(yīng)的IMP的時(shí)候,開發(fā)者可以通過重寫
- (id)forwardingTargetForSelector:(SEL)aSelector方法來“偷梁換柱”,把消息的接受者換成一個(gè)可以處理該消息的對(duì)象。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(Method:)){
        return otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

當(dāng)然也可以替換類方法,那就要重寫 + (id)forwardingTargetForSelector:(SEL)aSelector方法,返回值是一個(gè)類對(duì)象。

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(xxx)) {
        return NSClassFromString(@"Class name");
    }
    return [super forwardingTargetForSelector:aSelector];
}

我們將上面的代碼修改了下變成了這樣:
father:

#import <Foundation/Foundation.h>
@interface Father : NSObject
@end


#import "Father.h"
#import "Son.h"
@implementation Father

- (void)son {
    Son *s = [[Son alloc] init];
    [s performSelector:@selector(run)];
}

static void fatherRun() {
    NSLog(@"father is runing");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(run)) {
        class_addMethod([self class], sel, (IMP)fatherRun, "v@:@");
        return NO;
    }
    return [super resolveInstanceMethod:sel];
}

@end

son:

#import <Foundation/Foundation.h>
@interface Son : NSObject
@end


#import "Son.h"
#import <objc/runtime.h>
@implementation Son

- (void)eat {
    NSLog(@"son is eating");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    [self eat];
    return [Father new];
}
@end

打?。?son is eating
father is runing

這就是runtime的神奇之處,消息的接收者由“本應(yīng)該是”的Son轉(zhuǎn)變?yōu)榱薋ather。

這一步是替消息找備援接收者,如果這一步返回的是nil,那么補(bǔ)救措施就完全的失效了,Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對(duì)象。為接下來的完整的消息轉(zhuǎn)發(fā)生成一個(gè) NSMethodSignature對(duì)象。NSMethodSignature 對(duì)象會(huì)被包裝成 NSInvocation 對(duì)象,forwardInvocation:方法里就可以對(duì) NSInvocation 進(jìn)行處理了。

消息轉(zhuǎn)發(fā)第二步:如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了。此時(shí)會(huì)進(jìn)入完整轉(zhuǎn)發(fā)階段

2. 完整轉(zhuǎn)發(fā) forwardInvocation
我們只需要重寫下面這個(gè)方法,就可以自定義我們自己的轉(zhuǎn)發(fā)邏輯了。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
         [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

補(bǔ)充:NSMethodSignature 和 NSInvocation
在 iOS中可以直接調(diào)用某個(gè)對(duì)象的消息方式有兩種:

  • 一種是performSelector:withObject;
  • 再一種就是NSInvocation。

第一種方式比較簡單,能完成簡單的調(diào)用。但是對(duì)于>2個(gè)的參數(shù)或者有返回值的處理,那performSelector:withObject就顯得有點(diǎn)有心無力了,那么在這種情況下,我們就可以使用NSInvocation來進(jìn)行這些相對(duì)復(fù)雜的操作。
NSInvocation介紹:

  • NSInvocation中保存了方法所屬的對(duì)象/方法名稱/參數(shù)/返回值
  • NSInvocation就是將一個(gè)方法變成一個(gè)對(duì)象

NSMethodSignature:方法簽名類

  • 方法簽名類中保存了方法的名稱/參數(shù)/返回值,協(xié)同NSInvocation來進(jìn)行消息的轉(zhuǎn)發(fā)
  • 方法簽名類一般是用來設(shè)置參數(shù)和獲取返回值的, 和方法的調(diào)用沒有太大的關(guān)系

NSInvocation的基本使用
1.根據(jù)方法來初始化NSMethodSignature.方法簽名類

NSMethodSignature  *signature = [ViewController instanceMethodSignatureForSelector:@selector(run:)];

2.根據(jù)方法簽名類來創(chuàng)建NSInvocation對(duì)象

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//設(shè)置方法調(diào)用者
invocation.target = self;
//注意:這里的方法名一定要與方法簽名類中的方法一致
invocation.selector = @selector(run:);
NSString *way = @"byCar";
//這里的Index要從2開始,以為0跟1已經(jīng)被占據(jù)了,分別是self(target),selector(_cmd)
[invocation setArgument:&way atIndex:2];
//3、調(diào)用invoke方法
[invocation invoke];
//實(shí)現(xiàn)run:方法
- (void)run:(NSString *)method{

}

** NSMethodSignature 和 NSInvocation的基本使用補(bǔ)充結(jié)束!**

運(yùn)行時(shí)系統(tǒng)會(huì)在這一步給消息接收者最后一次機(jī)會(huì)將消息轉(zhuǎn)發(fā)給其它對(duì)象。對(duì)象會(huì)創(chuàng)建一個(gè)表示消息的NSInvocation對(duì)象,把與尚未處理的消息 有關(guān)的全部細(xì)節(jié)都封裝在anInvocation中,包括selector,目標(biāo)(target)和參數(shù)。我們可以在forwardInvocation 方法中選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象。

forwardInvocation:方法的實(shí)現(xiàn)有兩個(gè)任務(wù):

  • 定位可以響應(yīng)封裝在anInvocation中的消息的對(duì)象。這個(gè)對(duì)象不需要能處理所有未知消息。
  • 使用anInvocation作為參數(shù),將消息發(fā)送到選中的對(duì)象。anInvocation將會(huì)保留調(diào)用結(jié)果,運(yùn)行時(shí)系統(tǒng)會(huì)提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者。

不過,在這個(gè)方法中我們可以實(shí)現(xiàn)一些更復(fù)雜的功能,我們可以對(duì)消息的內(nèi)容進(jìn)行修改,比如追回一個(gè)參數(shù)等,然后再去觸發(fā)消息。另外,若發(fā)現(xiàn)某個(gè)消息不應(yīng)由本類處理,則應(yīng)調(diào)用父類的同名方法,以便繼承體系中的每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求。

還有一個(gè)很重要的問題,我們必須重寫以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

消息轉(zhuǎn)發(fā)機(jī)制使用從這個(gè)方法中獲取的信息來創(chuàng)建NSInvocation對(duì)象。因此我們必須重寫這個(gè)方法,為給定的selector提供一個(gè)合適的方法簽名。

完整的示例如下所示:
修改上面的代碼為:
father:

#import <Foundation/Foundation.h>
@interface Father : NSObject
@end


#import "Father.h"
#import "Son.h"
@implementation Father

- (void)son {
    Son *s = [[Son alloc] init];
    [s performSelector:@selector(run)];
}

- (void)run {
    NSLog(@"father is running");
}

@end

son:

#import <Foundation/Foundation.h>
@interface Son : NSObject
@end


#import "Son.h"
#import <objc/runtime.h>
@implementation Son

+ (BOOL)resolveInstanceMethod:(SEL)sel {

    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {

    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

    if ([NSStringFromSelector(aSelector) isEqualToString:@"run"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {

    Father *f = [[Father alloc] init];
    //改變selector
    [anInvocation setSelector:@selector(run)];
    //在這里指定消息接收者,如果不指定的話還是會(huì)拋出找不到方法的異常
    [anInvocation invokeWithTarget:f];
}

@end

打?。篺ather is running

我們已經(jīng)成功的將消息轉(zhuǎn)發(fā)給了Father實(shí)例。以上就是消息轉(zhuǎn)發(fā)的流程和具體實(shí)踐。

NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡單調(diào)用了doesNotRecognizeSelector:方法,它不會(huì)轉(zhuǎn)發(fā)任何消息。這樣,如果不在以上所述的三個(gè)步驟中處理未知消息,則會(huì)引發(fā)一個(gè)異常。

從某種意義上來講,forwardInvocation:就像一個(gè)未知消息的分發(fā)中心,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象?;蛘咭部梢韵褚粋€(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象。這取決于具體的實(shí)現(xiàn)。

實(shí)現(xiàn)此方法之后,若發(fā)現(xiàn)某調(diào)用不應(yīng)由本類處理,則會(huì)調(diào)用超類的同名方法。如此,繼承體系中的每個(gè)類都有機(jī)會(huì)處理該方法調(diào)用的請(qǐng)求,一直到NSObject根類。如果到NSObject也不能處理該條消息,那么就是再無挽救措施了,只能拋出“does Not Recognize Selector”異常了。

消息傳遞流程圖.jpg

至此,消息發(fā)送和轉(zhuǎn)發(fā)的過程結(jié)束。

4. 應(yīng)用場景

** 1.實(shí)現(xiàn)多繼承**
** 2.Method Swizzling(方法攪拌)**
** 3.Associated Object(關(guān)聯(lián)對(duì)象)**
** 4.動(dòng)態(tài)的增加方法**
** 5.NSCoding的自動(dòng)歸檔和自動(dòng)解檔**
** 6.字典和模型互相轉(zhuǎn)換**

1. 實(shí)現(xiàn)多繼承
根據(jù)上述任意消息轉(zhuǎn)發(fā)樣例,可知實(shí)現(xiàn)了在A中調(diào)用B中的方法b,大概可以猜到,這是不是類似繼承機(jī)制?答案是肯定的,因?yàn)镺C不支持多繼承,此處就給了一個(gè)實(shí)現(xiàn)多繼承的方式,因?yàn)槲覀兛梢詫?shí)現(xiàn)任意個(gè)類消息的轉(zhuǎn)發(fā)
通過forwardingTargetForSelector:方法,一個(gè)類可以做到繼承多個(gè)類的效果,只需要在這一步將消息轉(zhuǎn)發(fā)給正確的類對(duì)象就可以模擬多繼承的效果。
在OC程序中可以借用消息轉(zhuǎn)發(fā)機(jī)制來實(shí)現(xiàn)多繼承的功能。 一個(gè)對(duì)象對(duì)一個(gè)消息做出回應(yīng),類似于另一個(gè)對(duì)象中的方法借過來或是“繼承”過來一樣。A實(shí)例轉(zhuǎn)發(fā)了一個(gè)SEL消息到B實(shí)例中,執(zhí)行B中的SEL方法,結(jié)果看起來像是A實(shí)例執(zhí)行了一個(gè)和B實(shí)例一樣的SEL方法,其實(shí)執(zhí)行者還是B實(shí)例。

消息轉(zhuǎn)發(fā)提供了許多類似于多繼承的特性,但是他們之間有一個(gè)很大的不同:

  • 多繼承:合并了不同的行為特征在一個(gè)單獨(dú)的對(duì)象中,會(huì)得到一個(gè)重量級(jí)多層面的對(duì)象。
  • 消息轉(zhuǎn)發(fā):將各個(gè)功能分散到不同的對(duì)象中,得到的一些輕量級(jí)的對(duì)象,這些對(duì)象通過消息通過消息轉(zhuǎn)發(fā)聯(lián)合起來。

這里值得說明的一點(diǎn)是,即使我們利用轉(zhuǎn)發(fā)消息來實(shí)現(xiàn)了“假”繼承,但是NSObject類還是會(huì)將兩者區(qū)分開。像respondsToSelector:和 isKindOfClass:這類方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈。

如果非要制造假象,反應(yīng)出這種“假”的繼承關(guān)系,那么需要重新實(shí)現(xiàn) respondsToSelector:和 isKindOfClass:來加入你的轉(zhuǎn)發(fā)算法

如果一個(gè)對(duì)象轉(zhuǎn)發(fā)它接受的任何遠(yuǎn)程消息,它得給出一個(gè)methodSignatureForSelector:來返回準(zhǔn)確的方法描述,這個(gè)方法會(huì)最終響應(yīng)被轉(zhuǎn)發(fā)的消息。比如一個(gè)對(duì)象能給它的替代者對(duì)象轉(zhuǎn)發(fā)消息,它需要像下面這樣實(shí)現(xiàn)methodSignatureForSelector:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
        signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

需要引起注意的一點(diǎn),實(shí)現(xiàn)methodSignatureForSelector方法是一種先進(jìn)的技術(shù),只適用于沒有其他解決方案的情況下。它不會(huì)作為繼承的替代。如果您必須使用這種技術(shù),請(qǐng)確保您完全理解類做的轉(zhuǎn)發(fā)和您轉(zhuǎn)發(fā)的類的行為。請(qǐng)勿濫用!

2.Method Swizzling(方法攪拌)
它可以通過Runtime的API實(shí)現(xiàn)更改任意的方法,理論上可以在運(yùn)行時(shí)通過類名/方法名hook到任何 OC 方法,替換任何類的實(shí)現(xiàn)以及新增任意類。
核心函數(shù)就是method_exchangeImplementations
Method Swizzling原理:
Method Swizzling本質(zhì)上就是對(duì)IMP和SEL進(jìn)行交換。Method Swizzing是發(fā)生在運(yùn)行時(shí)的,主要用于在運(yùn)行時(shí)將兩個(gè)Method進(jìn)行交換,我們可以將Method Swizzling代碼寫到任何地方,但是只有在這段Method Swilzzling代碼執(zhí)行完畢之后互換才起作用。而且Method Swizzling也是iOS中AOP(面相切面編程)的一種實(shí)現(xiàn)方式,我們可以利用蘋果這一特性來實(shí)現(xiàn)AOP編程。

優(yōu)勢(shì):可以重寫某個(gè)方法而不用繼承,同時(shí)還可以調(diào)用原先的實(shí)現(xiàn)。通常的做法是在category中添加一個(gè)方法(當(dāng)然也可以是一個(gè)全新的class)。

具體場景應(yīng)用:替換兩個(gè)方法的實(shí)現(xiàn),可以做一些埋點(diǎn)、容錯(cuò)等工作;AFN也用到了,每一次resume,都會(huì)用到method swizzling。
一般我們使用都是新建一個(gè)分類,在分類中進(jìn)行Method Swizzling方法的交換:
具體場景應(yīng)用1:

#import <objc/runtime.h>
@implementation UIViewController (Swizzling)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}
@end

Method Swizzling可以在運(yùn)行時(shí)通過修改類的方法列表中selector對(duì)應(yīng)的函數(shù)或者設(shè)置交換方法實(shí)現(xiàn),來動(dòng)態(tài)修改方法。可以重寫某個(gè)方法而不用繼承,同時(shí)還可以調(diào)用原先的實(shí)現(xiàn)。所以通常應(yīng)用于在category中添加一個(gè)方法。

補(bǔ)充:在我們替換的方法- (void)xxx_viewWillAppear:(BOOL)animated中,調(diào)用了[self xxx_viewWillAppear:animated];這不是死循環(huán)了么?
其實(shí)這里并不會(huì)死循環(huán)。由于我們進(jìn)行了Swizzling,所以其實(shí)在原來的- (void)viewWillAppear:(BOOL)animated方法中,調(diào)用的是- (void)xxx_viewWillAppear:(BOOL)animated方法的實(shí)現(xiàn)。所以不會(huì)造成死循環(huán)。相反的,如果這里把[self xxx_viewWillAppear:animated];改成[self viewWillAppear:animated];就會(huì)造成死循環(huán)。因?yàn)橥饷嬲{(diào)用[self viewWillAppear:animated];的時(shí)候,會(huì)交換方法走到[self xxx_viewWillAppear:animated];這個(gè)方法實(shí)現(xiàn)中來,然后這里又去調(diào)用[self viewWillAppear:animated],就會(huì)造成死循環(huán)了。

具體場景應(yīng)用2:NSArray數(shù)組越界容錯(cuò)處理
常見做法是給NSArray,NSMutableArray增加分類,增加這些異常保護(hù)的方法,不過如果原有工程里面已經(jīng)寫了大量的AtIndex系列的方法,去替換成新的分類的方法,效率會(huì)比較低。這里可以考慮用Swizzling做。

#import "NSArray+ Swizzling.h"
#import "objc/runtime.h"
@implementation NSArray (Swizzling)
+ (void)load {
    Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(swizzling_objectAtIndex:));
    method_exchangeImplementations(fromMethod, toMethod);
}

- (id)swizzling_objectAtIndex:(NSUInteger)index {
    if (self.count-1 < index) {
        // 異常處理
        @try {
            return [self swizzling_objectAtIndex:index];
        }
        @catch (NSException *exception) {
            // 打印崩潰信息
            NSLog(@"---------- %s Crash Because Method %s  ----------\n", class_getName(self.class), __func__);
            NSLog(@"%@", [exception callStackSymbols]);
            return nil;
        }
        @finally {}
    } else {
        return [self swizzling_objectAtIndex:index];
    }
}
@end

注意:調(diào)用這個(gè)objc_getClass方法的時(shí)候,要先知道類對(duì)應(yīng)的真實(shí)的類名才行,NSArray其實(shí)在Runtime中對(duì)應(yīng)著__NSArrayI,NSMutableArray對(duì)應(yīng)著__NSArrayM,NSDictionary對(duì)應(yīng)著__NSDictionaryI,NSMutableDictionary對(duì)應(yīng)著__NSDictionaryM。

Method Swizzling注意點(diǎn):

  • ** Swizzling應(yīng)該總在+load中執(zhí)行;**
  • ** Swizzling應(yīng)該總是在dispatch_once中執(zhí)行:**
    Swizzling會(huì)改變?nèi)譅顟B(tài),所以在運(yùn)行時(shí)采取一些預(yù)防措施,使用dispatch_once就能夠確保代碼不管有多少線程都只被執(zhí)行一次。
    這里有一個(gè)很容易犯的錯(cuò)誤,那就是繼承中用了Swizzling。如果不寫dispatch_once就會(huì)導(dǎo)致Swizzling失效!
    舉個(gè)例子,比如同時(shí)對(duì)NSArray和NSMutableArray中的objectAtIndex:方法都進(jìn)行了Swizzling,這樣可能會(huì)導(dǎo)致NSArray中的Swizzling失效的。
    原因是:我們沒有用dispatch_once控制Swizzling只執(zhí)行一次。如果這段Swizzling被執(zhí)行多次,經(jīng)過多次的交換IMP和SEL之后,結(jié)果可能就是未交換之前的狀態(tài)。
    3. Swizzling在+load中執(zhí)行時(shí),不要調(diào)用[super load]
    原因同注意點(diǎn)二,如果是多繼承,并且對(duì)同一個(gè)方法都進(jìn)行了Swizzling,那么調(diào)用[super load]以后,父類的Swizzling就失效了。

兩個(gè)類方法區(qū)別
我們知道了 Objective-C 中絕大部分的類都繼承自 NSObject 類。而在 NSObject 類中有兩個(gè)非常特殊的類方法 +load 和 +initialize ,用于類的初始化。
+load

  • +load 方法是當(dāng)類或分類被添加到 Objective-C runtime 時(shí)被調(diào)用的,+load會(huì)在類初始加載時(shí)調(diào)用。
  • 子類的 +load 方法會(huì)在它的所有父類的 +load 方法之后執(zhí)行,而分類的 +load 方法會(huì)在它的主類的 +load 方法之后執(zhí)行。但是不同的類之間的 +load 方法的調(diào)用順序是不確定的。
  • 只調(diào)用一次
  • 不能使用Super且不沿用父類實(shí)現(xiàn)

+initialize

  • +initialize 方法是在類或它的子類收到第一條消息之前被調(diào)用的,這里所指的消息包括實(shí)例方法和類方法的調(diào)用。也就是說 +initialize 方法是以懶加載的方式被調(diào)用的,如果程序一直沒有給某個(gè)類或它的子類發(fā)送消息,那么這個(gè)類的 +initialize 方法是永遠(yuǎn)不會(huì)被調(diào)用的。
  • 調(diào)用次數(shù)多次

總結(jié)
+load 和 +initialize 調(diào)用機(jī)制和各自的特點(diǎn)

| | +load | +initialize
|-----------------
| 調(diào)用時(shí)機(jī) | 被添加到 runtime 時(shí) | 收到第一條消息前,可能永遠(yuǎn)不調(diào)用
| 調(diào)用順序 | 父類->子類->分類 | 父類->子類
| 調(diào)用次數(shù) | 1次 | 多次
| 是否需要顯式調(diào)用父類實(shí)現(xiàn) | 否 | 否
| 是否沿用父類的實(shí)現(xiàn) | 否 | 是
| 分類中的實(shí)現(xiàn) | 類和分類都執(zhí)行 | 覆蓋類中的方法,只執(zhí)行分類的實(shí)現(xiàn)

3. Associated Object(關(guān)聯(lián)對(duì)象)
Category
Category是表示一個(gè)指向分類的結(jié)構(gòu)體的指針.
需要注意的有兩點(diǎn):

  • category的方法沒有“完全替換掉”原來類已經(jīng)有的方法,也就是說如果category和原來類都有methodA,那么category附加完成之后,類的方法列表里會(huì)有兩個(gè)methodA
  • category的方法被放到了新方法列表的前面,而原來類的方法被放到了新方法列表的后面,這也就是我們平常所說的category的方法會(huì)“覆蓋”掉原來類的同名方法,這是因?yàn)檫\(yùn)行時(shí)在查找方法的時(shí)候是順著方法列表的順序查找的,它只要一找到對(duì)應(yīng)名字的方法,就會(huì)罷休,殊不知后面可能還有一樣名字的方法。

在 Category 中,我們無法添加@property,因?yàn)樘砑恿薂property之后并不會(huì)自動(dòng)幫我們生成實(shí)例變量以及存取方法。

關(guān)聯(lián)對(duì)象API在runtime里,所有的關(guān)聯(lián)對(duì)象都由AssociationsManager管理
使用objc_setAssociate()能夠?qū)⒁粋€(gè)變量通過指定的key值講實(shí)例與實(shí)例變量綁定在一起,在讀取的時(shí)候值調(diào)用objc_getAssociate(),在指定的實(shí)例中通過key將變量取出,可以簡單理解成字典一樣存取
這里涉及到了3個(gè)函數(shù):

//setter,就像字典中的 setValue:ForKey:
void objc_setAssociatedOject(id object, void *key, id value, objc_AssociationPolicy policy)

//getter,就像字典中的 objectForKey
id objc_getAssociatedObject(id object, void *key)

//remove,就像字典中的 removeAllObject
void objc_removeAssocaitedObjected(id object)

那么,我們現(xiàn)在就可以通過關(guān)聯(lián)對(duì)象來實(shí)現(xiàn)在 Category 中添加屬性的功能了。

// NSObject+AssociatedObject.h
@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end
// NSObject+AssociatedObject.m
@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
    objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 給系統(tǒng)NSObject類動(dòng)態(tài)添加屬性name

    NSObject *objc = [[NSObject alloc] init];
    objc.name = @"aaaaa";
    NSLog(@"%@",objc.name);

}


@end


// 定義關(guān)聯(lián)的key
static const char *key = "name";

@implementation NSObject (Property)

- (NSString *)name
{
    // 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
    // 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
    // 第二個(gè)參數(shù):關(guān)聯(lián)的key,通過這個(gè)key獲取
    // 第三個(gè)參數(shù):關(guān)聯(lián)的value
    // 第四個(gè)參數(shù):關(guān)聯(lián)的策略
    objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

關(guān)聯(lián)對(duì)象3種使用場景

  1. 為現(xiàn)有的類添加私有變量
  2. 為現(xiàn)有的類添加公有屬性
  3. 為KVO創(chuàng)建一個(gè)關(guān)聯(lián)的觀察者。

4.動(dòng)態(tài)的增加方法
在消息發(fā)送階段,如果在父類中也沒有找到相應(yīng)的IMP,就會(huì)執(zhí)行resolveInstanceMethod方法。在這個(gè)方法里面,我們可以動(dòng)態(tài)的給類對(duì)象或者實(shí)例對(duì)象動(dòng)態(tài)的增加方法。

當(dāng)你發(fā)送了一個(gè)object無法處理的消息時(shí)會(huì)發(fā)生什么呢?很明顯,"it breaks"。大多數(shù)情況下確實(shí)如此,但Cocoa和runtime也提供了一些應(yīng)對(duì)方法。
首先是動(dòng)態(tài)方法處理。通常來說,處理一個(gè)方法,運(yùn)行時(shí)尋找匹配的selector然后執(zhí)行之。有時(shí),你只想在運(yùn)行時(shí)才創(chuàng)建某個(gè)方法,比如有些信息只有在運(yùn)行時(shí)才能得到。要實(shí)現(xiàn)這個(gè)效果,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果確實(shí)增加了一個(gè)方法,記得返回YES。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"method1"]) {
        class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
    }
    return [super resolveInstanceMethod:sel];
}

5.NSCoding的自動(dòng)歸檔和自動(dòng)解檔
現(xiàn)在雖然手寫歸檔和解檔的時(shí)候不多了,但是自動(dòng)操作還是用Runtime來實(shí)現(xiàn)的。直接取出全部的實(shí)例變量列表+for循環(huán)。

手動(dòng)實(shí)現(xiàn):手動(dòng)的有一個(gè)缺陷,如果屬性多起來,要寫好多行相似的代碼,雖然功能是可以完美實(shí)現(xiàn),但是看上去不是很優(yōu)雅。

- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"name"];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}

用runtime實(shí)現(xiàn)的思路就比較簡單,我們循環(huán)依次找到每個(gè)成員變量的名稱,然后利用KVC讀取和賦值就可以完成encodeWithCoder和initWithCoder了。

#import "Student.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation Student

- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int outCount = 0;
    Ivar *vars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar var = vars[i];
        const char *name = ivar_getName(var);
        NSString *key = [NSString stringWithUTF8String:name];

        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}

- (nullable __kindof)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int outCount = 0;
        Ivar *vars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar var = vars[i];
            const char *name = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
    }
    return self;
}
@end
  • class_copyIvarList方法用來獲取當(dāng)前 Model 的所有成員變量
  • ivar_getName方法用來獲取每個(gè)成員變量的名稱。

6.字典和模型互相轉(zhuǎn)換

先來了解下KVC的底層原理:取出字典中的鍵值,去模型中找與之對(duì)應(yīng)的屬性

  1. 去模型中查找有沒有setValue:,直接調(diào)用這個(gè)對(duì)象setValue:賦值
  2. 如果沒有setValue:,就在模型中查找_value屬性
  3. 如果沒有_value屬性,就查找value屬性
  4. 如果還沒有就報(bào)錯(cuò)

利用runtime轉(zhuǎn)換原理:與KVC相反,先在模型中找到對(duì)應(yīng)的成員變量,然后去字典中找到對(duì)應(yīng)的數(shù)據(jù)進(jìn)行賦值。

  1. 獲取成員變量列表 :class_copyIvarList,進(jìn)而獲取模型中的所有實(shí)例變量
  • 將他們加入到一個(gè)數(shù)組當(dāng)中,然后遍歷數(shù)組,在遍歷過程中獲取字典中對(duì)應(yīng)的value給屬性對(duì)象賦值。

字典轉(zhuǎn)模型

  1. 調(diào)用 class_getProperty 方法獲取當(dāng)前 Model 的所有屬性。
  2. 調(diào)用 property_copyAttributeList 獲取屬性列表。
  3. 根據(jù)屬性名稱生成 setter 方法。
  4. 使用 objc_msgSend 調(diào)用 setter 方法為 Model 的屬性賦值(或者 KVC)
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 解析Plist文件
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];

    NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];

    // 獲取字典數(shù)組
    NSArray *dictArr = statusDict[@"statuses"];

    // 自動(dòng)生成模型的屬性字符串
//    [NSObject resolveDict:dictArr[0][@"user"]];


    _statuses = [NSMutableArray array];

    // 遍歷字典數(shù)組
    for (NSDictionary *dict in dictArr) {

        Status *status = [Status modelWithDict:dict];

        [_statuses addObject:status];

    }

    // 測(cè)試數(shù)據(jù)
    NSLog(@"%@ %@",_statuses,[_statuses[0] user]);


}

@end
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 思路:遍歷模型中所有屬性-》使用運(yùn)行時(shí)

    // 0.創(chuàng)建對(duì)應(yīng)的對(duì)象
    id objc = [[self alloc] init];

    // 1.利用runtime給對(duì)象中的成員屬性賦值

    // class_copyIvarList:獲取類中的所有成員屬性
    // Ivar:成員屬性的意思
    // 第一個(gè)參數(shù):表示獲取哪個(gè)類中的成員屬性
    // 第二個(gè)參數(shù):表示這個(gè)類有多少成員屬性,傳入一個(gè)Int變量地址,會(huì)自動(dòng)給這個(gè)變量賦值
    // 返回值Ivar *:指的是一個(gè)ivar數(shù)組,會(huì)把所有成員屬性放在一個(gè)數(shù)組中,通過返回的數(shù)組就能全部獲取到。
    /* 類似下面這種寫法

     Ivar ivar;
     Ivar ivar1;
     Ivar ivar2;
     // 定義一個(gè)ivar的數(shù)組a
     Ivar a[] = {ivar,ivar1,ivar2};

     // 用一個(gè)Ivar *指針指向數(shù)組第一個(gè)元素
     Ivar *ivarList = a;

     // 根據(jù)指針訪問數(shù)組第一個(gè)元素
     ivarList[0];

     */
    unsigned int count;

    // 獲取類中的所有成員屬性
    Ivar *ivarList = class_copyIvarList(self, &count);

    for (int i = 0; i < count; i++) {
        // 根據(jù)角標(biāo),從數(shù)組取出對(duì)應(yīng)的成員屬性
        Ivar ivar = ivarList[i];

        // 獲取成員屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 處理成員屬性名->字典中的key
        // 從第一個(gè)角標(biāo)開始截取
        NSString *key = [name substringFromIndex:1];

        // 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的value
        id value = dict[key];

        // 二級(jí)轉(zhuǎn)換:如果字典中還有字典,也需要把對(duì)應(yīng)的字典轉(zhuǎn)換成模型
        // 判斷下value是否是字典
        if ([value isKindOfClass:[NSDictionary class]]) {
            // 字典轉(zhuǎn)模型
            // 獲取模型的類對(duì)象,調(diào)用modelWithDict
            // 模型的類名已知,就是成員屬性的類型

            // 獲取成員屬性類型
           NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
          // 生成的是這種@"@\"User\"" 類型 -》 @"User"  在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思,不占用字符
            // 裁剪類型字符串
            NSRange range = [type rangeOfString:@"\""];

           type = [type substringFromIndex:range.location + range.length];

            range = [type rangeOfString:@"\""];

            // 裁剪到哪個(gè)角標(biāo),不包括當(dāng)前角標(biāo)
          type = [type substringToIndex:range.location];


            // 根據(jù)字符串類名生成類對(duì)象
            Class modelClass = NSClassFromString(type);


            if (modelClass) { // 有對(duì)應(yīng)的模型才需要轉(zhuǎn)

                // 把字典轉(zhuǎn)模型
                value  =  [modelClass modelWithDict:value];
            }


        }

        // 三級(jí)轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型.
        // 判斷值是否是數(shù)組
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對(duì)應(yīng)類有沒有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 轉(zhuǎn)換成id類型,就能調(diào)用任何對(duì)象的方法
                id idSelf = self;

                // 獲取數(shù)組中字典對(duì)應(yīng)的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
               Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                  id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型數(shù)組賦值給value
                value = arrM;

            }
        }


        if (value) { // 有值,才需要給模型的屬性賦值
            // 利用KVC給模型中的屬性賦值
            [objc setValue:value forKey:key];
        }

    }

    return objc;
}

@end

注意:這段代碼里面有一處判斷typeString的,這里判斷是防止model嵌套,比如說Student里面還有一層Student,那么這里就需要再次轉(zhuǎn)換一次,當(dāng)然這里有幾層就需要轉(zhuǎn)換幾次。

幾個(gè)出名的開源庫JSONModel、MJExtension等都是通過這種方式實(shí)現(xiàn)的(利用runtime的class_copyIvarList獲取屬性數(shù)組,遍歷模型對(duì)象的所有成員屬性,根據(jù)屬性名找到字典中key值進(jìn)行賦值,當(dāng)然這種方法只能解決NSString、NSNumber等,如果含有NSArray或NSDictionary,還要進(jìn)行第二步轉(zhuǎn)換,如果是字典數(shù)組,需要遍歷數(shù)組中的字典,利用objectWithDict方法將字典轉(zhuǎn)化為模型,在將模型放到數(shù)組中,最后把這個(gè)模型數(shù)組賦值給之前的字典數(shù)組)

模型轉(zhuǎn)字典
這里是上一部分字典轉(zhuǎn)模型的逆步驟:

  1. 調(diào)用 class_copyPropertyList 方法獲取當(dāng)前 Model 的所有屬性。
  2. 調(diào)用 property_getName 獲取屬性名稱。
  3. 根據(jù)屬性名稱生成 getter 方法。
  4. 使用 objc_msgSend 調(diào)用 getter 方法獲取屬性值(或者 KVC)
//模型轉(zhuǎn)字典
-(NSDictionary *)keyValuesWithObject{
    unsigned int outCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = propertyList[i];

        //生成getter方法,并用objc_msgSend調(diào)用
        const char *propertyName = property_getName(property);
        SEL getter = sel_registerName(propertyName);
        if ([self respondsToSelector:getter]) {
            id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);

            /*判斷當(dāng)前屬性是不是Model*/
            if ([value isKindOfClass:[self class]] && value) {
                value = [value keyValuesWithObject];
            }

            if (value) {
                NSString *key = [NSString stringWithUTF8String:propertyName];
                [dict setObject:value forKey:key];
            }
        }

    }
    free(propertyList);
    return dict;
}

注:中間注釋那里的判斷也是防止model嵌套,如果model里面還有一層model,那么model轉(zhuǎn)字典的時(shí)候還需要再次轉(zhuǎn)換,同樣,有幾層就需要轉(zhuǎn)換幾次。
不過上述的做法是假設(shè)字典里面不再包含二級(jí)字典,如果還包含數(shù)組,數(shù)組里面再包含字典,那還需要多級(jí)轉(zhuǎn)換。

7.動(dòng)態(tài)獲取 class 和 slector
比較基礎(chǔ)的一個(gè)動(dòng)態(tài)特性是通過String來生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起來很簡單:

Class stringclass = NSClassFromString(@"NSString");
NSString *myString = [stringclass stringWithString:@"Hello World"];
NSClassFromString(@"MyClass");
NSSelectorFromString(@"showShareActionSheet");

在NSObject協(xié)議中,有以下5個(gè)方法,我們可以通過NSObject的一些方法獲取運(yùn)行時(shí)信息或動(dòng)態(tài)執(zhí)行一些消息:

// 獲取對(duì)象對(duì)應(yīng)的class
- (Class)class;
// 判斷一個(gè)對(duì)象或者類是不是某個(gè)class或者這個(gè)class的派生類
- (BOOL)isKindOfClass:(Class)aClass;
// 判斷是否是該類的實(shí)例,不包括子類或者父類;
- (BOOL)isMemberOfClass:(Class)aClass;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面是否實(shí)現(xiàn)了某個(gè)協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 判斷一個(gè)對(duì)象或者類對(duì)應(yīng)的objc_class里面有沒有某個(gè)方法
- (BOOL)respondsToSelector:(SEL)aSelector;

自動(dòng)打印屬性字符串

    @implementation NSObject (Log)


// 自動(dòng)打印屬性字符串
+ (void)resolveDict:(NSDictionary *)dict{

    // 拼接屬性字符串代碼
    NSMutableString *strM = [NSMutableString string];

    // 1.遍歷字典,把字典中的所有key取出來,生成對(duì)應(yīng)的屬性代碼
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

        // 類型經(jīng)常變,抽出來
         NSString *type;

        if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
            type = @"NSString";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
            type = @"NSArray";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
            type = @"int";
        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
            type = @"NSDictionary";
        }


        // 屬性字符串
        NSString *str;
        if ([type containsString:@"NS"]) {
            str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
        }else{
            str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
        }

        // 每生成屬性字符串,就自動(dòng)換行。
        [strM appendFormat:@"\n%@\n",str];

    }];

    // 把拼接好的字符串打印出來,就好了。
    NSLog(@"%@",strM);
}
@end
5.Runtime缺點(diǎn)及Runtime常用函數(shù)**

1.危險(xiǎn)性主要體現(xiàn)以下幾個(gè)方面:

  • Method swizzling不是原子性操作。如果在+load方法里面寫,是沒有問題的,但是如果寫在+initialize方法中就會(huì)出問題。

  • 調(diào)用super方法會(huì)出問題
    如果你在一個(gè)類中重寫一個(gè)方法,并且不調(diào)用super方法,你可能會(huì)導(dǎo)致一些問題出現(xiàn)。在大多數(shù)情況下,super方法是期望被調(diào)用的(除非有特殊說明)。如果你使用同樣的思想來進(jìn)行Swizzling,可能就會(huì)引起很多問題。如果你不調(diào)用原始的方法實(shí)現(xiàn),那么你Swizzling改變的太多了,而導(dǎo)致整個(gè)程序變得不安全。

2. 日常可能用的比較多的Runtime函數(shù)可能就是下面這些

//獲取cls類對(duì)象所有成員ivar結(jié)構(gòu)體
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//獲取cls類對(duì)象name對(duì)應(yīng)的實(shí)例方法結(jié)構(gòu)體
Method class_getInstanceMethod(Class cls, SEL name)
//獲取cls類對(duì)象name對(duì)應(yīng)類方法結(jié)構(gòu)體
Method class_getClassMethod(Class cls, SEL name)
//獲取cls類對(duì)象name對(duì)應(yīng)方法imp實(shí)現(xiàn)
IMP class_getMethodImplementation(Class cls, SEL name)
//測(cè)試cls對(duì)應(yīng)的實(shí)例是否響應(yīng)sel對(duì)應(yīng)的方法
BOOL class_respondsToSelector(Class cls, SEL sel)
//獲取cls對(duì)應(yīng)方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//測(cè)試cls是否遵守protocol協(xié)議
BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
//為cls類對(duì)象添加新方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//替換cls類對(duì)象中name對(duì)應(yīng)方法的實(shí)現(xiàn)
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//為cls添加新成員
BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
//為cls添加新屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
//獲取m對(duì)應(yīng)的選擇器
SEL method_getName(Method m)
//獲取m對(duì)應(yīng)的方法實(shí)現(xiàn)的imp指針
IMP method_getImplementation(Method m)
//獲取m方法的對(duì)應(yīng)編碼
const char *method_getTypeEncoding(Method m)
//獲取m方法參數(shù)的個(gè)數(shù)
unsigned int method_getNumberOfArguments(Method m)
//copy方法返回值類型
char *method_copyReturnType(Method m)
//獲取m方法index索引參數(shù)的類型
char *method_copyArgumentType(Method m, unsigned int index)
//獲取m方法返回值類型
void method_getReturnType(Method m, char *dst, size_t dst_len)
//獲取方法的參數(shù)類型
void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len)
//設(shè)置m方法的具體實(shí)現(xiàn)指針
IMP method_setImplementation(Method m, IMP imp)
//交換m1,m2方法對(duì)應(yīng)具體實(shí)現(xiàn)的函數(shù)指針
void method_exchangeImplementations(Method m1, Method m2)
//獲取v的名稱
const char *ivar_getName(Ivar v)
//獲取v的類型編碼
const char *ivar_getTypeEncoding(Ivar v)
//設(shè)置object對(duì)象關(guān)聯(lián)的對(duì)象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
//獲取object關(guān)聯(lián)的對(duì)象
id objc_getAssociatedObject(id object, const void *key)
//移除object關(guān)聯(lián)的對(duì)象
void objc_removeAssociatedObjects(id object)
這些API看上去不好記,其實(shí)使用的時(shí)候不難,關(guān)于方法操作的,一般都是method開頭,關(guān)于類的,一般都是class開頭的,其他的基本都是objc開頭的,剩下的就看代碼補(bǔ)全的提示,看方法名基本就能找到想要的方法了。當(dāng)然很熟悉的話,可以直接打出指定方法,也不會(huì)依賴代碼補(bǔ)全。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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