Runtime原理探究

一、Runtime簡(jiǎn)介

Runtime簡(jiǎn)稱運(yùn)行時(shí)沐批。OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制蝎亚,其中最主要的是消息機(jī)制。

  • 對(duì)于C語(yǔ)言发框,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)躺彬。
  • 對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)宪拥,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用仿野。
  • 事實(shí)證明:
    • 在編譯階段,OC可以調(diào)用任何函數(shù)她君,即使這個(gè)函數(shù)并未實(shí)現(xiàn)脚作,只要聲明過就不會(huì)報(bào)錯(cuò)。
    • 在編譯階段缔刹,C語(yǔ)言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)鳖枕。
  • 如果向某個(gè)對(duì)象傳遞消息,在底層桨螺,所有的方法都是普通的C語(yǔ)言函數(shù),然而對(duì)象收到消息之后酿秸,究竟該調(diào)用哪個(gè)方法則完全取決于運(yùn)行期決定灭翔,甚至可能在運(yùn)行期改變,這些特性使得Objective-C變成一門真正的動(dòng)態(tài)語(yǔ)言辣苏。
  • 在Runtime中肝箱,對(duì)象可以用C語(yǔ)言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來(lái)實(shí)現(xiàn)稀蟋,另外再加上了一些額外的特性煌张。這些結(jié)構(gòu)體和函數(shù)被Runtime函數(shù)封裝后,讓OC的面向?qū)ο缶幊套優(yōu)榭赡堋?/li>

二退客、Objective-C中的數(shù)據(jù)結(jié)構(gòu)

描述Objective-C對(duì)象所有的數(shù)據(jù)結(jié)構(gòu)定義都在Runtime的頭文件里骏融,下面我們逐一分析。

1.id

運(yùn)行期系統(tǒng)如何知道某個(gè)對(duì)象的類型呢萌狂?對(duì)象類型并不是在編譯期就知道了档玻,而是要在運(yùn)行期查找。Objective-C有個(gè)特殊的類型id茫藏,它可以表示Objective-C的任意對(duì)象類型误趴,id類型定義在Runtime的頭文件中:

struct objc_object {
    Class isa;
} *id;

由此可見,每個(gè)對(duì)象結(jié)構(gòu)體的首個(gè)成員是Class類的變量务傲。該變量定義了對(duì)象所屬的類凉当,通常稱為isa指針。

objc_object

objc_object是表示一個(gè)類的實(shí)例的結(jié)構(gòu)體
它的定義如下(objc/objc.h):

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

可以看到售葡,這個(gè)結(jié)構(gòu)體只有一個(gè)字體看杭,即指向其類的isa指針。這樣天通,當(dāng)我們向一個(gè)Objective-C對(duì)象發(fā)送消息時(shí)泊窘,運(yùn)行時(shí)庫(kù)會(huì)根據(jù)實(shí)例對(duì)象的isa指針找到這個(gè)實(shí)例對(duì)象所屬的類。Runtime庫(kù)會(huì)在類的方法列表及父類的方法列表中去尋找與消息對(duì)應(yīng)的selector指向的方法,找到后即運(yùn)行這個(gè)方法烘豹。

2.Class

Class對(duì)象也定義在Runtime的頭文件中,查看objc/runtime.h中的objc_class結(jié)構(gòu)體:
Objective-C中,類是由Class類型來(lái)表示的瓜贾,它實(shí)際上是一個(gè)指
向objc_class結(jié)構(gòu)體的指針。

typedef struct objc_class *Class;

struct objc_class { 
    Class isa                                 OBJC_ISA_AVAILABILITY; 
#if !__OBJC2__
    Class super_class                         OBJC2_UNAVAILABLE;   // 父類
    const char *name                          OBJC2_UNAVAILABLE;   // 類名
    long version                              OBJC2_UNAVAILABLE;   // 類的版本信息携悯,默認(rèn)為0
    long info                                 OBJC2_UNAVAILABLE;   // 類信息祭芦,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size                        OBJC2_UNAVAILABLE;   // 該類的實(shí)例變量大小
    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;   // 協(xié)議鏈表
#endif
}

下面說(shuō)下Class的結(jié)構(gòu)體中的幾個(gè)主要變量:

  • 1.isa:
    結(jié)構(gòu)體的首個(gè)變量也是isa指針,這說(shuō)明Class本身也是Objective-C中的對(duì)象憔鬼。isa指針非常重要, 對(duì)象需要通過isa指針找到它的類, 類需要通過isa找到它的元類. 這在調(diào)用實(shí)例方法和類方法的時(shí)候起到重要的作用.
  • 2.super_class:
    結(jié)構(gòu)體里還有個(gè)變量是super_class龟劲,它定義了本類的超類。類對(duì)象所屬類型(isa指針?biāo)赶虻念愋停┦橇硗庖粋€(gè)類轴或,叫做“元類”昌跌。
  • 3.ivars:
    成員變量列表,類的成員變量都在ivars里面照雁。
  • 4.methodLists:
    方法列表蚕愤,類的實(shí)例方法都在methodLists里,類方法在元類的methodLists里面饺蚊。methodLists是一個(gè)指針的指針萍诱,通過修改該指針指向指針的值,就可以動(dòng)態(tài)的為某一個(gè)類添加成員方法污呼。這也就是Category實(shí)現(xiàn)的原理裕坊,同時(shí)也說(shuō)明了Category只可以為對(duì)象添加成員方法,不能添加成員變量燕酷。
  • 5.cache:
    方法緩存列表籍凝,objc_msgSend(下文詳解)每調(diào)用一次方法后,就會(huì)把該方法緩存到cache列表中悟狱,下次調(diào)用的時(shí)候静浴,會(huì)優(yōu)先從cache列表中尋找,如果cache沒有挤渐,才從methodLists中查找方法苹享。提高效率。

元類(Meta Class)

meta-class是一個(gè)類對(duì)象的類浴麻。
在上面我們提到得问,所有的類自身也是一個(gè)對(duì)象,我們可以向這個(gè)對(duì)象發(fā)送消息(即調(diào)用類方法)软免。既然是對(duì)象宫纬,那么它也是一個(gè)objc_object指針,它包含一個(gè)指向其類的一個(gè)isa指針膏萧。那么漓骚,這個(gè)isa指針指向什么呢蝌衔?
為了調(diào)用類方法,這個(gè)類的isa指針必須指向一個(gè)包含這些類方法的一個(gè)objc_class結(jié)構(gòu)體蝌蹂。這就引出了meta-class的概念噩斟,meta-class中存儲(chǔ)著一個(gè)類的所有類方法。
所以孤个,調(diào)用類方法的這個(gè)類對(duì)象的isa指針指向的就是meta-class
當(dāng)我們向一個(gè)對(duì)象發(fā)送消息時(shí)剃允,runtime會(huì)在這個(gè)對(duì)象所屬的這個(gè)類的方法列表中查找方法;而向一個(gè)類發(fā)送消息時(shí)齐鲤,會(huì)在這個(gè)類的meta-class的方法列表中查找斥废。

再深入一下,meta-class也是一個(gè)類给郊,也可以向它發(fā)送一個(gè)消息牡肉,那么它的isa又是指向什么呢?為了不讓這種結(jié)構(gòu)無(wú)限延伸下去淆九,Objective-C的設(shè)計(jì)者讓所有的meta-class的isa指向基類的meta-class荚板,以此作為它們的所屬類。

即吩屹,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己拧抖。

通過上面的描述煤搜,再加上對(duì)objc_class結(jié)構(gòu)體中super_class指針的分析,我們就可以描繪出類及相應(yīng)meta-class類的一個(gè)繼承體系了唧席,如下代碼

image.png

看圖說(shuō)話:
上圖中:superclass指針代表繼承關(guān)系擦盾,isa指針代表實(shí)例所屬的類。
類也是一個(gè)對(duì)象淌哟,它是另外一個(gè)類的實(shí)例迹卢,這個(gè)就是“元類”,元類里面保存了類方法的列表徒仓,類里面保存了實(shí)例方法的列表腐碱。實(shí)例對(duì)象的isa指向類,類對(duì)象的isa指向元類掉弛,元類對(duì)象的isa指針指向一個(gè)“根元類”(root metaclass)症见。所有子類的元類都繼承父類的元類,換而言之殃饿,類對(duì)象和元類對(duì)象有著同樣的繼承關(guān)系乎芳。

1.Class是一個(gè)指向objc_class結(jié)構(gòu)體的指針帖池,而id是一個(gè)指向objc_object結(jié)構(gòu)體的指針睡汹,其中的isa是一個(gè)指向objc_class結(jié)構(gòu)體的指針。其中的id就是我們所說(shuō)的對(duì)象攒钳,Class就是我們所說(shuō)的類帮孔。
2.isa指針不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來(lái)確定類型不撑,而是應(yīng)該用isKindOfClass:方法來(lái)確定實(shí)例對(duì)象的類文兢。因?yàn)镵VO的實(shí)現(xiàn)機(jī)制就是將被觀察對(duì)象的isa指針指向一個(gè)中間類而不是真實(shí)的類。

Category

Category是表示一個(gè)指向分類的結(jié)構(gòu)體的指針焕檬,其定義如下:

typedef struct objc_category *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; // 分類所實(shí)現(xiàn)的協(xié)議列表
}

這個(gè)結(jié)構(gòu)體主要包含了分類定義的實(shí)例方法與類方法姆坚,其中instance_methods列表是objc_class中方法列表的一個(gè)子集,而class_methods列表是元類方法列表的一個(gè)子集实愚。
可發(fā)現(xiàn)兼呵,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實(shí)例變量和屬性

struct objc_ivar_list *ivars             OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表

3.SEL

//// http://www.reibang.com/p/3e050ec3b759
SEL是選擇子的類型腊敲,選擇子指的就是方法的名字击喂。在Runtime的頭文件中的定義如下:

typedef struct objc_selector *SEL;

它就是個(gè)映射到方法的C字符串,SEL類型代表著方法的簽名碰辅,在類對(duì)象的方法列表中存儲(chǔ)著該簽名與方法代碼的對(duì)應(yīng)關(guān)系懂昂,每個(gè)方法都有一個(gè)與之對(duì)應(yīng)的SEL類型的對(duì)象,根據(jù)一個(gè)SEL對(duì)象就可以找到方法的地址没宾,進(jìn)而調(diào)用方法。
////http://www.reibang.com/p/adf0d566c887
SEL又叫選擇器铲敛,是表示一個(gè)方法的selector的指針迁酸,其定義如下:
方法的selector用于表示運(yùn)行時(shí)方法的名字胁出。Objective-C在編譯時(shí)闹蒜,會(huì)依據(jù)每一個(gè)方法的名字姥闪、參數(shù)序列,生成一個(gè)唯一的整型標(biāo)識(shí)(Int類型的地址)避归,這個(gè)標(biāo)識(shí)就是SEL。
兩個(gè)類之間,只要方法名相同奸柬,那么方法的SEL就是一樣的,每一個(gè)方法都對(duì)應(yīng)著一個(gè)SEL。所以在Objective-C同一個(gè)類(及類的繼承體系)中蹲蒲,不能存在2個(gè)同名的方法,即使參數(shù)類型不同也不行
如在某一個(gè)類中定義以下兩個(gè)方法: 錯(cuò)誤

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;

當(dāng)然,不同的類可以擁有相同的selector表锻,這個(gè)沒有問題显歧。不同類的實(shí)例對(duì)象執(zhí)行相同的selector時(shí),會(huì)在各自的方法列表中去根據(jù)selector去尋找自己對(duì)應(yīng)的IMP。
工程中的所有的SEL組成一個(gè)Set集合巨缘,如果我們想到這個(gè)方法集合中查找某個(gè)方法時(shí),只需要去找到這個(gè)方法對(duì)應(yīng)的SEL就行了拴清,SEL實(shí)際上就是根據(jù)方法名hash化了的一個(gè)字符串,而對(duì)于字符串的比較僅僅需要比較他們的地址就可以了,可以說(shuō)速度上無(wú)語(yǔ)倫比木张!
本質(zhì)上,SEL只是一個(gè)指向方法的指針(準(zhǔn)確的說(shuō)妻献,只是一個(gè)根據(jù)方法名hash化了的KEY值,能唯一代表一個(gè)方法),它的存在只是為了加快方法的查詢速度锹引。
@selector()就是取類方法的編號(hào)
通過下面三種方法可以獲取SEL:
a、sel_registerName函數(shù)
b腾啥、Objective-C編譯器提供的@selector()
c、NSSelectorFromString()方法

4.Method

Method代表類中的某個(gè)方法的類型,在Runtime的頭文件中的定義如下:

typedef struct objc_method *Method;

objc_method的結(jié)構(gòu)體定義如下:

struct objc_method{
    SEL method_name      OBJC2_UNAVAILABLE; // 方法名
    char *method_types   OBJC2_UNAVAILABLE;
    IMP method_imp       OBJC2_UNAVAILABLE; // 方法實(shí)現(xiàn)
}
  • 1.method_name:方法名渐苏。
  • 2.method_types:方法類型鞠眉,主要存儲(chǔ)著方法的參數(shù)類型和返回值類型。
  • 3.IMP:方法的實(shí)現(xiàn)哗戈,函數(shù)指針暇仲。(下文詳解)
    class_copyMethodList(Class cls, unsigned int *outCount)可以使用這個(gè)方法獲取某個(gè)類的成員方法列表全度。

////
Method用于表示類定義中的方法
我們可以看到該結(jié)構(gòu)體中包含一個(gè)SEL和IMP勉盅,實(shí)際上相當(dāng)于在SEL和IMP之間作了一個(gè)映射。有了SEL痒筒,我們便可以找到對(duì)應(yīng)的IMP宰闰,從而調(diào)用方法的實(shí)現(xiàn)代碼。

5.Ivar

Ivar代表類中實(shí)例變量的類型簿透,在Runtime的頭文件中的定義如下:

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; 
#ifdef __LP64__
    int space                         OBJC2_UNAVAILABLE;
#endif
}

class_copyIvarList(Class cls, unsigned int *outCount) 可以使用這個(gè)方法獲取某個(gè)類的成員變量列表移袍。

6.objc_property_t

objc_property_t是屬性,在Runtime的頭文件中的的定義如下:

typedef struct objc_property *objc_property_t;

class_copyPropertyList(Class cls, unsigned int *outCount) 可以使用這個(gè)方法獲取某個(gè)類的屬性列表老充。

7.IMP

IMP在Runtime的頭文件中的的定義如下:

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

IMP是一個(gè)函數(shù)指針,它是由編譯器生成的树枫。當(dāng)你發(fā)起一個(gè)消息后庄呈,這個(gè)函數(shù)指針決定了最終執(zhí)行哪段代碼因块。
////
IMP實(shí)際上是一個(gè)函數(shù)指針,指向方法實(shí)現(xiàn)的地址温兼。
其定義如下:

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

第一個(gè)參數(shù):是指向self的指針(如果是實(shí)例方法,則是類實(shí)例的內(nèi)存地址登淘;如果是類方法,則是指向元類的指針)
第二個(gè)參數(shù):是方法選擇器(selector)
接下來(lái)的參數(shù):方法的參數(shù)列表。

前面介紹過的SEL就是為了查找方法的最終實(shí)現(xiàn)IMP的。由于每個(gè)方法對(duì)應(yīng)唯一的SEL,因此我們可以通過SEL方便快速準(zhǔn)確地獲得它所對(duì)應(yīng)的IMP侣夷,查找過程將在下面討論。取得IMP后刻像,我們就獲得了執(zhí)行這個(gè)方法代碼的入口點(diǎn)嗓违,此時(shí)制恍,我們就可以像調(diào)用普通的C語(yǔ)言函數(shù)一樣來(lái)使用這個(gè)函數(shù)指針了。

8.Cache

Cache在Runtime的頭文件中的的定義如下:

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

每調(diào)用一次方法后秘噪,不會(huì)直接在isa指向的類的方法列表(methodLists)中遍歷查找能夠響應(yīng)消息的方法,因?yàn)檫@樣效率太低勉耀。它會(huì)把該方法緩存到cache列表中指煎,下次的時(shí)候,就直接優(yōu)先從cache列表中尋找便斥,如果cache沒有至壤,才從isa指向的類的方法列表(methodLists)中查找方法。提高效率枢纠。

三.發(fā)送消息(objc_msgSend)

在Objective-C中像街,調(diào)用方法是經(jīng)常使用的。用Objective-C的術(shù)語(yǔ)來(lái)說(shuō)晋渺,這叫做“傳遞消息”(pass a message)镰绎。消息有“名稱”(name)或者“選擇子”(selector),也可以接受參數(shù)木西,而且可能還有返回值畴栖。
如果向某個(gè)對(duì)象傳遞消息,在底層八千,所有的方法都是普通的C語(yǔ)言函數(shù)吗讶,然而對(duì)象收到消息之后,究竟該調(diào)用哪個(gè)方法則完全取決于運(yùn)行期決定恋捆,甚至可能在運(yùn)行期改變照皆,這些特性使得Objective-C變成一門真正的動(dòng)態(tài)語(yǔ)言。
給對(duì)象發(fā)送消息可以這樣來(lái)寫:

id returnValue = [someObject message:parm];

someObject叫做“接收者”(receiver)沸停,message是“選擇子”(selector)膜毁,選擇子和參數(shù)結(jié)合起來(lái)就叫做“消息”(message)。編譯器看到此消息后愤钾,將其轉(zhuǎn)換成C語(yǔ)言函數(shù)調(diào)用爽茴,所調(diào)用的函數(shù)乃是消息傳遞機(jī)制中的核心函數(shù),叫做objc_msgSend绰垂,其原型如下:

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

后面的...表示這是個(gè)“參數(shù)個(gè)數(shù)可變的函數(shù)”室奏,能接受兩個(gè)或兩個(gè)以上的參數(shù)。第一個(gè)參數(shù)是接收者(receiver)劲装,第二個(gè)參數(shù)是選擇子(selector)胧沫,后續(xù)參數(shù)就是消息中傳遞的那些參數(shù)(parm)昌简,其順序不變。

編譯器會(huì)把上面的那個(gè)消息轉(zhuǎn)換成:

id returnValue objc_mgSend(someObject, @selector(message:), parm);

objc_msgSend發(fā)送消息的原理:

  • 第一步:檢測(cè)這個(gè)selector是不是要被忽略的绒怨。
  • 第二步:檢測(cè)這個(gè)target對(duì)象是不是nil對(duì)象纯赎。(nil對(duì)象執(zhí)行任何一個(gè)方法都不會(huì)Crash,因?yàn)闀?huì)被忽略掉)
  • 第三步:首先會(huì)根據(jù)target(objc_object)對(duì)象的isa指針獲取它所對(duì)應(yīng)的類(objc_class)南蹂。
  • 第四步:查看緩存中是否存在方法犬金,系統(tǒng)把近期發(fā)送過的消息記錄在其中,蘋果認(rèn)為這樣可以提高效率: 優(yōu)先在類(class)的cache里面查找是否有與選擇子(selector)名稱相符的方法六剥。
    如果有晚顷,則找到objc_method中的IMP類型(函數(shù)指針)的成員method_imp去找到實(shí)現(xiàn)內(nèi)容,并執(zhí)行;
    如果緩存中沒有命中疗疟,那么到該類的方法表(methodLists)查找該方法该默,依次從后往前查找。
  • 第五步:如果沒有在類(class)找到策彤,再到父類(super_class)查找栓袖,直至根類。
  • 第六步:一旦找到與選擇子(selector)名稱相符的方法店诗,就跳至其實(shí)現(xiàn)代碼裹刮。
  • 第七步: 如果沒有找到,就會(huì)執(zhí)行消息轉(zhuǎn)發(fā)(message forwarding)的第一步動(dòng)態(tài)解析庞瘸。

如果是調(diào)用類方法
objc_class中的isa指向該類的元類(metaclass)
如果是調(diào)用類方法的話必指,那么就會(huì)利用objc_class中的成員isa找到元類(metaclass),然后尋找方法恕洲,直至根metaclass,沒有找到的話則仍然進(jìn)入動(dòng)態(tài)解析塔橡。

#import <objc/message.h>
// 創(chuàng)建person對(duì)象
    Person *p = [[Person alloc] init];
    
    // 調(diào)用對(duì)象方法
    [p eat];
    
    // 本質(zhì):讓對(duì)象發(fā)送消息
    objc_msgSend(p, @selector(eat));

    // 調(diào)用類方法的方式:兩種
    // 第一種通過類名調(diào)用
    [Person eat];
    // 第二種通過類對(duì)象調(diào)用
    [[Person class] eat];
    
    // 用類名調(diào)用類方法,底層會(huì)自動(dòng)把類名轉(zhuǎn)換成類對(duì)象調(diào)用
    // 本質(zhì):讓類對(duì)象發(fā)送消息
    objc_msgSend([Person class], @selector(eat));
  • 消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)


    image.png

四.消息轉(zhuǎn)發(fā)(message forwarding)

當(dāng)一個(gè)對(duì)象能接收一個(gè)消息時(shí)霜第,就會(huì)走正常的方法調(diào)用流程葛家。但如果一個(gè)對(duì)象無(wú)法接收指定消息時(shí),又會(huì)發(fā)生什么事呢泌类?默認(rèn)情況下癞谒,如果是以[object message]的方式調(diào)用方法,如果object無(wú)法響應(yīng)message消息時(shí)刃榨,編譯器會(huì)報(bào)錯(cuò)弹砚。但如果是以perform...的形式來(lái)調(diào)用,則需要等到運(yùn)行時(shí)才能確定object是否能接收message消息枢希。如果不能桌吃,則程序崩潰。

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

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

不過,我們這邊想討論下不使用respondsToSelector:判斷的情況瑟俭。這才是我們這一節(jié)的重點(diǎn)翎卓。

當(dāng)一個(gè)對(duì)象無(wú)法接收某一消息時(shí),就會(huì)啟動(dòng)所謂消息轉(zhuǎn)發(fā)(message forwarding)機(jī)制摆寄,通過這一機(jī)制失暴,我們可以告訴對(duì)象如何處理未知的消息。默認(rèn)情況下微饥,對(duì)象接收到未知的消息逗扒,會(huì)導(dǎo)致程序崩潰,通過控制臺(tái)畜号,我們可以看到以下異常信息:

-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'

這段異常信息實(shí)際上是由NSObject的doesNotRecognizeSelector方法拋出的。不過允瞧,我們可以采取一些措施简软,讓我們的程序執(zhí)行特定的邏輯,而避免程序的崩潰述暂。
消息轉(zhuǎn)發(fā)機(jī)制基本上分為三個(gè)步驟:

  • 1.動(dòng)態(tài)方法解析
  • 2.備用接收者
  • 3.完整轉(zhuǎn)發(fā)
    下面我們?cè)敿?xì)討論一下這三個(gè)步驟痹升。

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

對(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)添加到類里面就可以了察郁。如下代碼所示:

void functionForMethod1(id self, SEL _cmd) {
   NSLog(@"%@, %p", self, _cmd);
}
    
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selectorString = NSStringFromSelector(sel);
    if ([selectorString isEqualToString:@"method1"]) {
        class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
    }
    return [super resolveInstanceMethod:sel];
}
void otherEat(id self, SEL cmd) {
    NSLog(@"blog.yoonangel.com");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        class_addMethod(self, sel, (IMP)otherEat, "v@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

class_addMethod方法可謂是核心,那么依次來(lái)看他的參數(shù)的含義:

  • first:添加到哪個(gè)類
  • second:添加方法的方法編號(hào)(選擇子)
  • third:添加方法的函數(shù)實(shí)現(xiàn)(IMP函數(shù)指針)
  • fourth:IMP指針指向的函數(shù)返回值和參數(shù)類型
    v代表無(wú)返回值void @代表id類型對(duì)象->self :代表選擇子SEL->_cmd
    • "v@:" v代表無(wú)返回值void转唉,如果是i則代表int 無(wú)參數(shù)
    • "i@:" 代表返回值是int類型皮钠,無(wú)參數(shù)
    • "v@:i@:" 代表返回值是void類型,參數(shù)是int類型赠法,存在一個(gè)參數(shù)(多參數(shù)依次累加)"v@:@@" 代表 兩個(gè)參數(shù)的沒有返回值麦轰。

這種方案更多的是為了實(shí)現(xiàn)@dynamic屬性。

備用接收者

如果在上一步無(wú)法處理消息砖织,則Runtime會(huì)繼續(xù)調(diào)以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

如果一個(gè)對(duì)象實(shí)現(xiàn)了這個(gè)方法款侵,并返回一個(gè)非nil的結(jié)果,則這個(gè)對(duì)象會(huì)作為消息的新接收者侧纯,且消息會(huì)被分發(fā)到這個(gè)對(duì)象新锈。當(dāng)然這個(gè)對(duì)象不能是self自身,否則就是出現(xiàn)無(wú)限循環(huán)眶熬。當(dāng)然壕鹉,如果我們沒有指定相應(yīng)的對(duì)象來(lái)處理aSelector剃幌,則應(yīng)該調(diào)用父類的實(shí)現(xiàn)來(lái)返回結(jié)果。

使用這個(gè)方法通常是在對(duì)象內(nèi)部晾浴,可能還有一系列其它對(duì)象能處理該消息负乡,我們便可借這些對(duì)象來(lái)處理消息并返回,這樣在對(duì)象外部看來(lái)脊凰,還是由該對(duì)象親自處理了這一消息抖棘。如下代碼所示:

@interface SUTRuntimeMethodHelper : NSObject
- (void)method2;
@end
@implementation SUTRuntimeMethodHelper
- (void)method2 {
    NSLog(@"%@, %p", self, _cmd);
}
@end
#pragma mark -
@interface SUTRuntimeMethod () {
    SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype)object {
    return [[self alloc] init];
}
- (instancetype)init {
    self = [super init];
    if (self != nil) {
        _helper = [[SUTRuntimeMethodHelper alloc] init];
    }
    return self;
}
- (void)test {
    [self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"forwardingTargetForSelector");
    NSString *selectorString = NSStringFromSelector(aSelector);
    // 將消息轉(zhuǎn)發(fā)給_helper來(lái)處理
    if ([selectorString isEqualToString:@"method2"]) {
        return _helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上。但這一步無(wú)法對(duì)消息進(jìn)行處理狸涌,如操作消息的參數(shù)和返回值切省。

完整消息轉(zhuǎn)發(fā)

如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機(jī)制了帕胆。此時(shí)會(huì)調(diào)用以下方法:

- (void)forwardInvocation:(NSInvocation *)anInvocation

運(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ù):

  • 1.定位可以響應(yīng)封裝在anInvocation中的消息的對(duì)象儒老。這個(gè)對(duì)象不需要能處理所有未知消息。
  • 2.使用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è)方法中獲取的信息來(lái)創(chuàng)建NSInvocation對(duì)象。因此我們必須重寫這個(gè)方法稳衬,為給定的selector提供一個(gè)合適的方法簽名霞捡。
完整的示例如下所示:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
            signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_helper];
    }
}

NSObject的forwardInvocation:方法實(shí)現(xiàn)只是簡(jiǎn)單調(diào)用了doesNotRecognizeSelector:方法,它不會(huì)轉(zhuǎn)發(fā)任何消息薄疚。這樣碧信,如果不在以上所述的三個(gè)步驟中處理未知消息赊琳,則會(huì)引發(fā)一個(gè)異常。
從某種意義上來(lái)講砰碴,forwardInvocation:就像一個(gè)未知消息的分發(fā)中心躏筏,將這些未知的消息轉(zhuǎn)發(fā)給其它對(duì)象〕释鳎或者也可以像一個(gè)運(yùn)輸站一樣將所有未知消息都發(fā)送給同一個(gè)接收對(duì)象趁尼。這取決于具體的實(shí)現(xiàn)。

消息轉(zhuǎn)發(fā)與多重繼承

回過頭來(lái)看第二和第三步猖辫,通過這兩個(gè)方法我們可以允許一個(gè)對(duì)象與其它對(duì)象建立關(guān)系酥泞,以處理某些未知消息,而表面上看仍然是該對(duì)象在處理消息啃憎。通過這種關(guān)系芝囤,我們可以模擬“多重繼承”的某些特性,讓對(duì)象可以“繼承”其它對(duì)象的特性來(lái)處理一些事情辛萍。不過悯姊,這兩者間有一個(gè)重要的區(qū)別:多重繼承將不同的功能集成到一個(gè)對(duì)象中,它會(huì)讓對(duì)象變得過大叹阔,涉及的東西過多挠轴;而消息轉(zhuǎn)發(fā)將功能分解到獨(dú)立的小的對(duì)象中传睹,并通過某種方式將這些對(duì)象連接起來(lái)耳幢,并做相應(yīng)的消息轉(zhuǎn)發(fā)。
不過消息轉(zhuǎn)發(fā)雖然類似于繼承欧啤,但NSObject的一些方法還是能區(qū)分兩者睛藻。如respondsToSelector:isKindOfClass:只能用于繼承體系,而不能用于轉(zhuǎn)發(fā)鏈邢隧。便如果我們想讓這種消息轉(zhuǎn)發(fā)看起來(lái)像是繼承店印,則可以重寫這些方法,如以下代碼所示:

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector])
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;  
}

當(dāng)一個(gè)對(duì)象在收到無(wú)法解讀的消息之后倒慧,它會(huì)將消息實(shí)施轉(zhuǎn)發(fā)按摘。轉(zhuǎn)發(fā)的主要步驟如下:

消息轉(zhuǎn)發(fā)步驟:

  • 第一步:對(duì)象在收到無(wú)法解讀的消息后,首先調(diào)用resolveInstanceMethod:方法決定是否動(dòng)態(tài)添加方法纫谅。如果返回YES炫贤,則調(diào)用class_addMethod動(dòng)態(tài)添加方法,消息得到處理付秕,結(jié)束兰珍;如果返回NO,則進(jìn)入下一步询吴;
  • 第二步:當(dāng)前接收者還有第二次機(jī)會(huì)處理未知的選擇子掠河,在這一步中亮元,運(yùn)行期系統(tǒng)會(huì)問:能不能把這條消息轉(zhuǎn)給其他接收者來(lái)處理。會(huì)進(jìn)入forwardingTargetForSelector:方法唠摹,用于指定備選對(duì)象響應(yīng)這個(gè)selector爆捞,不能指定為self。如果返回某個(gè)對(duì)象則會(huì)調(diào)用對(duì)象的方法跃闹,結(jié)束嵌削。如果返回nil,則進(jìn)入下一步望艺;
  • 第三步:這步我們要通過methodSignatureForSelector:方法簽名苛秕,如果返回nil,則消息無(wú)法處理找默。如果返回methodSignature艇劫,則進(jìn)入下一步;
  • 第四步:這步調(diào)用forwardInvocation:方法惩激,我們可以通過anInvocation對(duì)象做很多處理店煞,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對(duì)象等风钻,如果方法調(diào)用成功顷蟀,則結(jié)束。如果失敗骡技,則進(jìn)入doesNotRecognizeSelector方法鸣个,拋出異常,此異常表示選擇子最終未能得到處理布朦。
/**
 消息轉(zhuǎn)發(fā)第一步:對(duì)象在收到無(wú)法解讀的消息后囤萤,首先調(diào)用此方法,可用于動(dòng)態(tài)添加方法是趴,方法決定是否動(dòng)態(tài)添加方法涛舍。如果返回YES,則調(diào)用class_addMethod動(dòng)態(tài)添加方法唆途,消息得到處理富雅,結(jié)束;如果返回NO肛搬,則進(jìn)入下一步没佑;
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}

/**
 當(dāng)前接收者還有第二次機(jī)會(huì)處理未知的選擇子,在這一步中滚婉,運(yùn)行期系統(tǒng)會(huì)問:能不能把這條消息轉(zhuǎn)給其他接收者來(lái)處理图筹。會(huì)進(jìn)入此方法,用于指定備選對(duì)象響應(yīng)這個(gè)selector,不能指定為self远剩。如果返回某個(gè)對(duì)象則會(huì)調(diào)用對(duì)象的方法扣溺,結(jié)束。如果返回nil瓜晤,則進(jìn)入下一步锥余;
 */
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}

/**
 這步我們要通過該方法簽名,如果返回nil痢掠,則消息無(wú)法處理驱犹。如果返回methodSignature,則進(jìn)入下一步足画。
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"study"])
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

/**
 這步調(diào)用該方法雄驹,我們可以通過anInvocation對(duì)象做很多處理,比如修改實(shí)現(xiàn)方法淹辞,修改響應(yīng)對(duì)象等医舆,如果方法調(diào)用成功,則結(jié)束象缀。如果失敗蔬将,則進(jìn)入doesNotRecognizeSelector方法。
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setSelector:@selector(play)];
    [anInvocation invokeWithTarget:self];
}

/**
 拋出異常央星,此異常表示選擇子最終未能得到處理霞怀。
 */
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"無(wú)法處理消息:%@", NSStringFromSelector(aSelector));
}
image.png
image.png

接收者在每一步中均有機(jī)會(huì)處理消息,步驟越靠后莉给,處理消息的代價(jià)越大毙石。最好在第一步就能處理完,這樣系統(tǒng)就可以把此方法緩存起來(lái)了禁谦。

五.關(guān)聯(lián)對(duì)象 (AssociatedObject)

使用場(chǎng)景:
可以在類別中添加屬性
有時(shí)我們需要在對(duì)象中存放相關(guān)信息胁黑,Objective-C中有一種強(qiáng)大的特性可以解決此類問題废封,就是“關(guān)聯(lián)對(duì)象”州泊。
可以給某個(gè)對(duì)象關(guān)聯(lián)許多其他對(duì)象,這些對(duì)象通過“鍵”來(lái)區(qū)分漂洋。存儲(chǔ)對(duì)象值時(shí)遥皂,可以指明“存儲(chǔ)策略”,用以維護(hù)相應(yīng)地“內(nèi)存管理語(yǔ)義”刽漂。存儲(chǔ)策略由名為“objc_AssociationPolicy” 的枚舉所定義演训。下表中列出了該枚舉值得取值,同時(shí)還列出了與之等下的@property屬性:假如關(guān)聯(lián)對(duì)象成為了屬性贝咙,那么他就會(huì)具備對(duì)應(yīng)的語(yǔ)義样悟。
1.設(shè)置關(guān)聯(lián)值
參數(shù)說(shuō)明:
object:與誰(shuí)關(guān)聯(lián),通常是傳self
key:唯一鍵,在獲取值時(shí)通過該鍵獲取窟她,通常是使用static
const void *來(lái)聲明
value:關(guān)聯(lián)所設(shè)置的值
policy:內(nèi)存管理策略陈症,比如使用copy

// 以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值。
void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)

2.獲取關(guān)聯(lián)值
參數(shù)說(shuō)明:
object:與誰(shuí)關(guān)聯(lián)震糖,通常是傳self录肯,在設(shè)置關(guān)聯(lián)時(shí)所指定的與哪個(gè)對(duì)象關(guān)聯(lián)的那個(gè)對(duì)象
key:唯一鍵,在設(shè)置關(guān)聯(lián)時(shí)所指定的鍵

// 根據(jù)給定的鍵從某對(duì)象中獲取對(duì)應(yīng)的對(duì)象值吊说。
id objc_getAssociatedObject(id object, const void *key)

3.取消關(guān)聯(lián)

// 移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象论咏。
void objc_removeAssociatedObjects(id object)

關(guān)聯(lián)策略

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){
OBJC_ASSOCIATION_ASSIGN = 0,             // 表示弱引用關(guān)聯(lián),通常是基本數(shù)據(jù)類型 @property (assign) or @ property (unsafe_unretained)
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,   // 表示強(qiáng)引用關(guān)聯(lián)對(duì)象颁井,是線程安全的 @property (nonatomic, strong)
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,     // 表示關(guān)聯(lián)對(duì)象copy厅贪,是線程安全的 @property (nonatomic, copy)
OBJC_ASSOCIATION_RETAIN = 01401,         // 表示強(qiáng)引用關(guān)聯(lián)對(duì)象,不是線程安全的 @property (atomic, strong)
OBJC_ASSOCIATION_COPY = 01403            // 表示關(guān)聯(lián)對(duì)象copy雅宾,不是線程安全的 @property (atomic, copy)
};

六.交換方法(method swizzing)

  • 開發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠卦溢,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能秀又。

  • 方式一:繼承系統(tǒng)的類单寂,重寫方法.

  • 方式二:使用runtime,交換方法.
    在Objective-C中,對(duì)象收到消息之后吐辙,究竟會(huì)調(diào)用哪種方法需要在運(yùn)行期才能解析出來(lái)宣决。查找消息的唯一依據(jù)是選擇子(selector),選擇子(selector)與相應(yīng)的方法(IMP)對(duì)應(yīng)昏苏,利用Objective-C的動(dòng)態(tài)特性尊沸,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換選擇子(selector)對(duì)應(yīng)的方法實(shí)現(xiàn),這就是方法交換(method swizzling)贤惯。
    每個(gè)類都有一個(gè)方法列表洼专,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系。IMP有點(diǎn)類似函數(shù)指針孵构,指向具體的Method實(shí)現(xiàn)屁商。

  • 交換原理:

    • 交換之前:


      image.png
    • 交換之后:


      image.png
類的方法列表會(huì)把每個(gè)選擇子都映射到相關(guān)的IMP之上

image.png

我們可以新增選擇子,也可以改變某個(gè)選擇子所對(duì)應(yīng)的方法實(shí)現(xiàn)颈墅,還可以交換兩個(gè)選擇子所映射到的指針蜡镶。

Objective-C中提供了三種API來(lái)動(dòng)態(tài)替換類方法或?qū)嵗椒ǖ膶?shí)現(xiàn):

  • 1.class_replaceMethod替換類方法的定義。
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
  • 2.method_exchangeImplementations交換兩個(gè)方法的實(shí)現(xiàn)恤筛。
method_exchangeImplementations(Method m1, Method m2)
  • 3.method_setImplementation設(shè)置一個(gè)方法的實(shí)現(xiàn)
method_setImplementation(Method m, IMP imp)

先說(shuō)下這三個(gè)方法的區(qū)別:

  • class_replaceMethod:當(dāng)類中沒有想替換的原方法時(shí)官还,該方法調(diào)用class_addMethod來(lái)為該類增加一個(gè)新方法,也正因如此毒坛,class_replaceMethod在調(diào)用時(shí)需要傳入types參數(shù)望伦,而其余兩個(gè)卻不需要林说。
  • method_exchangeImplementations:內(nèi)部實(shí)現(xiàn)就是調(diào)用了兩次method_setImplementation方法。
    再來(lái)看看他們的使用場(chǎng)景:
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        SEL originalSelector = @selector(willMoveToSuperview:);
        SEL swizzledSelector = @selector(myWillMoveToSuperview:);

        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        
        BOOL didAddMethod = class_addMethod(self, 
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(self, 
                                swizzledSelector, 
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
    NSLog(@"WillMoveToSuperview: %@", self); 
    [self myWillMoveToSuperview:newSuperview];
}
@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    // 需求:給imageNamed方法提供功能愕掏,每次加載圖片就判斷下圖片是否加載成功。
    // 步驟一:先搞個(gè)分類,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
    // 步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn),就能調(diào)用imageWithName,間接調(diào)用imageWithName的實(shí)現(xiàn)。
    UIImage *image = [UIImage imageNamed:@"123"];
    
}

@end


@implementation UIImage (Image)
// 加載分類到內(nèi)存的時(shí)候調(diào)用
+ (void)load
{
    // 交換方法
    
    // 獲取imageWithName方法地址
    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
    
    // 獲取imageName方法地址
    Method imageName = class_getClassMethod(self, @selector(imageNamed:));

    // 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式
    method_exchangeImplementations(imageWithName, imageName);
    
    
}

// 不能在分類中重寫系統(tǒng)方法imageNamed,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.

// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
   
    // 這里調(diào)用imageWithName屎勘,相當(dāng)于調(diào)用imageName
    UIImage *image = [self imageWithName:name];
    
    if (image == nil) {
        NSLog(@"加載空的圖片");
    }
    
    return image;
}


@end
+ (void)load{

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

Method objectAtIndex = class_getInstanceMethod(self, @selector(objectAtIndex:));
Method db_objectAtIndex = class_getInstanceMethod(self, @selector(db_objectAtIndex:));

method_exchangeImplementations(objectAtIndex, db_objectAtIndex);
});

}

- (id)db_objectAtIndex:(NSUInteger)inex{
NSLog(@"%s",__FUNCTION__);
id item;
if ( self.count > inex ) {
item = [self db_objectAtIndex:inex];
}
else{
item = nil;
}
return item;
}

總結(jié)

1.class_replaceMethod竿裂,當(dāng)需要替換的方法有可能不存在時(shí),可以考慮使用該方法影斑。
2.method_exchangeImplementations残邀,當(dāng)需要交換兩個(gè)方法的時(shí)使用。
3.method_setImplementation是最簡(jiǎn)單的用法遗嗽,當(dāng)僅僅需要為一個(gè)方法設(shè)置其實(shí)現(xiàn)方式時(shí)實(shí)現(xiàn)。

Swizzling應(yīng)該總是在+load中執(zhí)行

在Objective-C中,運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法。+load會(huì)在類初始加載時(shí)調(diào)用,+initialize會(huì)在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用。這兩個(gè)方法是可選的寸癌,且只有在實(shí)現(xiàn)了它們時(shí)才會(huì)被調(diào)用吮旅。由于method swizzling會(huì)影響到類的全局狀態(tài),因此要盡量避免在并發(fā)處理中出現(xiàn)競(jìng)爭(zhēng)的情況。+load能保證在類的初始化過程中被加載聂受,并保證這種改變應(yīng)用級(jí)別的行為的一致性碗旅。相比之下,+initialize在其執(zhí)行時(shí)不提供這種保證–事實(shí)上祟辟,如果在應(yīng)用中沒為給這個(gè)類發(fā)送消息医瘫,則它可能永遠(yuǎn)不會(huì)被調(diào)用。

Swizzling應(yīng)該總是在dispatch_once中執(zhí)行

與上面相同旧困,因?yàn)?code>swizzling會(huì)改變?nèi)譅顟B(tài)醇份,所以我們需要在運(yùn)行時(shí)采取一些預(yù)防措施。原子性就是這樣一種措施吼具,它確保代碼只被執(zhí)行一次僚纷,不管有多少個(gè)線程。GCD的dispatch_once可以確保這種行為拗盒,我們應(yīng)該將其作為method swizzling的最佳實(shí)踐怖竭。

選擇器、方法與實(shí)現(xiàn)

在Objective-C中锣咒,選擇器(selector)侵状、方法(method)和實(shí)現(xiàn)(implementation)是運(yùn)行時(shí)中一個(gè)特殊點(diǎn)赞弥,雖然在一般情況下毅整,這些術(shù)語(yǔ)更多的是用在消息發(fā)送的過程描述中趣兄。

以下是Objective-C Runtime Reference中的對(duì)這幾個(gè)術(shù)語(yǔ)一些描述:

  1. Selector(typedef struct objc_selector *SEL):用于在運(yùn)行時(shí)中表示一個(gè)方法的名稱。一個(gè)方法選擇器是一個(gè)C字符串悼嫉,它是在Objective-C運(yùn)行時(shí)被注冊(cè)的艇潭。選擇器由編譯器生成,并且在類被加載時(shí)由運(yùn)行時(shí)自動(dòng)做映射操作戏蔑。
  2. Method(typedef struct objc_method *Method):在類定義中表示方法的類型
  3. Implementation(typedef id (*IMP)(id, SEL, ...)):這是一個(gè)指針類型蹋凝,指向方法實(shí)現(xiàn)函數(shù)的開始位置。這個(gè)函數(shù)使用為當(dāng)前CPU架構(gòu)實(shí)現(xiàn)的標(biāo)準(zhǔn)C調(diào)用規(guī)范总棵。每一個(gè)參數(shù)是指向?qū)ο笞陨淼闹羔?self)鳍寂,第二個(gè)參數(shù)是方法選擇器。然后是方法的實(shí)際參數(shù)情龄。

理解這幾個(gè)術(shù)語(yǔ)之間的關(guān)系最好的方式是:一個(gè)類維護(hù)一個(gè)運(yùn)行時(shí)可接收的消息分發(fā)表迄汛;分發(fā)表中的每個(gè)入口是一個(gè)方法(Method),其中key是一個(gè)特定名稱骤视,即選擇器(SEL)鞍爱,其對(duì)應(yīng)一個(gè)實(shí)現(xiàn)(IMP),即指向底層C函數(shù)的指針专酗。

為了swizzle一個(gè)方法睹逃,我們可以在分發(fā)表中將一個(gè)方法的現(xiàn)有的選擇器映射到不同的實(shí)現(xiàn),而將該選擇器對(duì)應(yīng)的原始實(shí)現(xiàn)關(guān)聯(lián)到一個(gè)新的選擇器中祷肯。

在 Cocoa 編程中沉填,大部分的類都繼承于 NSObject ,有些 NSObject 提供的方法僅僅是為了查詢運(yùn)動(dòng)時(shí)系統(tǒng)的相關(guān)信息佑笋,這此方法都可以反查自己拜轨。比如 -isKindOfClass:-isMemberOfClass: 都是用于查詢?cè)诶^承體系中的位置。 -respondsToSelector:指明是否接受特定的消息允青。 +conformsToProtocol: 指明是否要求實(shí)現(xiàn)在指定的協(xié)議中聲明的方法橄碾。 -methodForSelector:提供方法實(shí)現(xiàn)的地址。

簡(jiǎn)單概括下Runtime的方法列表和用法

  1. objc_getClass 獲取類名
  2. objc_msgSend 調(diào)用對(duì)象的sel
  3. class_getClassMethod 獲取類方法
  4. method_exchangeImplementations 交換兩個(gè)方法
  5. class_addMethod 給類添加方法
  6. class_copyIvarList 獲取成員變量信息
  7. class_copyPropertyList 獲取屬性信息
  8. class_copyMethodList 獲取方法信息
  9. class_copyProtocolList 獲取協(xié)議信息
  10. objc_setAssociatedObject 動(dòng)態(tài)關(guān)聯(lián)set方法
  11. objc_getAssociatedObject 動(dòng)態(tài)關(guān)聯(lián)get方法
  12. ivar_getName 獲取變量名char *類型
  13. ivar_getTypeEncoding 獲取到屬性變量的類型詳情類型介紹

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颠锉,一起剝皮案震驚了整個(gè)濱河市法牲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琼掠,老刑警劉巖拒垃,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瓷蛙,居然都是意外死亡悼瓮,警方通過查閱死者的電腦和手機(jī)戈毒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)横堡,“玉大人埋市,你說(shuō)我怎么就攤上這事∶” “怎么了道宅?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胸蛛。 經(jīng)常有香客問我污茵,道長(zhǎng),這世上最難降的妖魔是什么葬项? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任泞当,我火速辦了婚禮,結(jié)果婚禮上民珍,老公的妹妹穿的比我還像新娘襟士。我一直安慰自己,他們只是感情好穷缤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布敌蜂。 她就那樣靜靜地躺著,像睡著了一般津肛。 火紅的嫁衣襯著肌膚如雪章喉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天身坐,我揣著相機(jī)與錄音秸脱,去河邊找鬼。 笑死部蛇,一個(gè)胖子當(dāng)著我的面吹牛摊唇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播涯鲁,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼巷查,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了抹腿?” 一聲冷哼從身側(cè)響起岛请,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎警绩,沒想到半個(gè)月后崇败,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年后室,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缩膝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡岸霹,死狀恐怖疾层,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情松申,我是刑警寧澤云芦,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布俯逾,位于F島的核電站贸桶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏桌肴。R本人自食惡果不足惜皇筛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坠七。 院中可真熱鬧水醋,春花似錦、人聲如沸彪置。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春唾琼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背线婚。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工匈织, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人授舟。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓救恨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親释树。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肠槽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評(píng)論 0 9
  • 我們常常會(huì)聽說(shuō) Objective-C 是一門動(dòng)態(tài)語(yǔ)言奢啥,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢秸仙?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,176評(píng)論 0 7
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,544評(píng)論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂樂的簡(jiǎn)書閱讀 2,131評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 728評(píng)論 0 2