Objective-C Runtime

本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼,具備了靈活的動(dòng)態(tài)特性,使這門古老的語(yǔ)言煥發(fā)生機(jī)。主要內(nèi)容如下:
引言
簡(jiǎn)介
與Runtime交互
Runtime術(shù)語(yǔ)
消息
動(dòng)態(tài)方法解析
消息轉(zhuǎn)發(fā)
健壯的實(shí)例變量(Non Fragile ivars)
Objective-C Associated Objects
Method Swizzling
總結(jié)

引言
曾經(jīng)覺得Objective-C特別方便上手,面對(duì)著 Cocoa 中大量 API,只知道簡(jiǎn)單的查文檔和調(diào)用。還記得初學(xué) Objective-C 時(shí)把[receiver message]當(dāng)成簡(jiǎn)單的方法調(diào)用,而無(wú)視了“發(fā)送消息”這句話的深刻含義。其實(shí)[receiver message]會(huì)被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)

如果消息含有參數(shù),則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能夠找到對(duì)應(yīng)的selector,那么就相當(dāng)于直接執(zhí)行了接收者這個(gè)對(duì)象的特定方法;否則,消息要么被轉(zhuǎn)發(fā),或是臨時(shí)向接收者動(dòng)態(tài)添加這個(gè)selector對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容,要么就干脆玩完崩潰掉。
現(xiàn)在可以看出[receiver message]真的不是一個(gè)簡(jiǎn)簡(jiǎn)單單的方法調(diào)用。因?yàn)檫@只是在編譯階段確定了要向接收者發(fā)送message這條消息,而receive將要如何響應(yīng)這條消息,那就要看運(yùn)行時(shí)發(fā)生的情況來(lái)決定了。
Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性,這些深層次的知識(shí)雖然平時(shí)寫代碼用的少一些,但是卻是每個(gè) Objective-C 程序員需要了解的。
簡(jiǎn)介
因?yàn)镺bjective-C是一門動(dòng)態(tài)語(yǔ)言,所以它總是想辦法把一些決定工作從編譯連接推遲到運(yùn)行時(shí)。也就是說(shuō)只有編譯器是不夠的,還需要一個(gè)運(yùn)行時(shí)系統(tǒng) (runtime system) 來(lái)執(zhí)行編譯后的代碼。這就是 Objective-C Runtime 系統(tǒng)存在的意義,它是整個(gè)Objective-C運(yùn)行框架的一塊基石。
Runtime其實(shí)有兩個(gè)版本:“modern”和 “l(fā)egacy”。我們現(xiàn)在用的 Objective-C 2.0 采用的是現(xiàn)行(Modern)版的Runtime系統(tǒng),只能運(yùn)行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X較老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統(tǒng)。這兩個(gè)版本最大的區(qū)別在于當(dāng)你更改一個(gè)類的實(shí)例變量的布局時(shí),在早期版本中你需要重新編譯它的子類,而現(xiàn)行版就不需要。
Runtime基本是用C和匯編寫的,可見蘋果為了動(dòng)態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護(hù)的開源代碼。蘋果和GNU各自維護(hù)一個(gè)開源的runtime版本,這兩個(gè)版本之間都在努力的保持一致。
與Runtime交互
Objective-C 從三種不同的層級(jí)上與 Runtime 系統(tǒng)進(jìn)行交互,分別是通過(guò) Objective-C 源代碼,通過(guò) Foundation 框架的NSObject類定義的方法,通過(guò)對(duì) runtime 函數(shù)的直接調(diào)用。
Objective-C源代碼
大部分情況下你就只管寫你的Objective-C代碼就行,runtime 系統(tǒng)自動(dòng)在幕后辛勤勞作著。
還記得引言中舉的例子吧,消息的執(zhí)行會(huì)使用到一些編譯器為實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言特性而創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)和函數(shù),Objective-C中的類、方法和協(xié)議等在 runtime 中都由一些數(shù)據(jù)結(jié)構(gòu)來(lái)定義,這些內(nèi)容在后面會(huì)講到。(比如objc_msgSend函數(shù)及其參數(shù)列表中的id和SEL都是啥)
NSObject的方法
Cocoa 中大多數(shù)類都繼承于NSObject類,也就自然繼承了它的方法。最特殊的例外是NSProxy,它是個(gè)抽象超類,它實(shí)現(xiàn)了一些消息轉(zhuǎn)發(fā)有關(guān)的方法,可以通過(guò)繼承它來(lái)實(shí)現(xiàn)一個(gè)其他類的替身類或是虛擬出一個(gè)不存在的類,說(shuō)白了就是領(lǐng)導(dǎo)把自己展現(xiàn)給大家風(fēng)光無(wú)限,但是把活兒都交給幕后小弟去干。
有的NSObject中的方法起到了抽象接口的作用,比如description方法需要你重載它并為你定義的類提供描述內(nèi)容。NSObject還有些方法能在運(yùn)行時(shí)獲得類的信息,并檢查一些特性,比如class返回對(duì)象的類;isKindOfClass:和isMemberOfClass:則檢查對(duì)象是否在指定的類繼承體系中;respondsToSelector:檢查對(duì)象能否響應(yīng)指定的消息;conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;methodForSelector:則返回指定方法實(shí)現(xiàn)的地址。
Runtime的函數(shù)
Runtime 系統(tǒng)是一個(gè)由一系列函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成,具有公共接口的動(dòng)態(tài)共享庫(kù)。頭文件存放于/usr/include/objc目錄下。許多函數(shù)允許你用純C代碼來(lái)重復(fù)實(shí)現(xiàn) Objective-C 中同樣的功能。雖然有一些方法構(gòu)成了NSObject類的基礎(chǔ),但是你在寫 Objective-C 代碼時(shí)一般不會(huì)直接用到這些函數(shù)的,除非是寫一些 Objective-C 與其他語(yǔ)言的橋接或是底層的debug工作。在Objective-C Runtime Reference中有對(duì) Runtime 函數(shù)的詳細(xì)文檔。
Runtime術(shù)語(yǔ)
還記得引言中的objc_msgSend:方法吧,它的真身是這樣的:
id objc_msgSend ( id self, SEL op, ... );

下面將會(huì)逐漸展開介紹一些術(shù)語(yǔ),其實(shí)它們都對(duì)應(yīng)著數(shù)據(jù)結(jié)構(gòu)。
SEL
objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objective-C中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的 ID,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;

其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objective-C 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來(lái)獲得一個(gè)SEL類型的方法選擇器。
不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器,于是 Objective-C 中方法命名有時(shí)會(huì)帶上參數(shù)類型(NSNumber一堆抽象工廠方法拿走不謝),Cocoa 中有好多長(zhǎng)長(zhǎng)的方法哦。
id
objc_msgSend第一個(gè)參數(shù)類型為id,大家對(duì)它都不陌生,它是一個(gè)指向類實(shí)例的指針:
typedef struct objc_object *id;

那objc_object又是啥呢:
struct objc_object { Class isa; };

objc_object結(jié)構(gòu)體包含一個(gè)isa指針,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類。
PS:isa指針不總是指向?qū)嵗龑?duì)象所屬的類,不能依靠它來(lái)確定類型,而是應(yīng)該用class方法來(lái)確定實(shí)例對(duì)象的類。因?yàn)镵VO的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的isa指針指向一個(gè)中間類而不是真實(shí)的類,這是一種叫做 isa-swizzling 的技術(shù),詳見官方文檔
Class
之所以說(shuō)isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:
1

typedef struct objc_class *Class;

而objc_class就是我們摸到的那個(gè)瓜,里面的東西多著呢:
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;

可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協(xié)議。
PS:OBJC2_UNAVAILABLE之類的宏定義是蘋果在 Objective-C 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的黑魔法,為的是兼容非Objective-C 2.0的遺留邏輯,但我們?nèi)阅軓闹蝎@得一些有價(jià)值的信息,有興趣的可以查看源代碼。
Objective-C 2.0 的頭文件雖然沒暴露出objc_class結(jié)構(gòu)體更詳細(xì)的設(shè)計(jì),我們依然可以從Objective-C 1.0 的定義中小窺端倪:
在objc_class結(jié)構(gòu)體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說(shuō)可以動(dòng)態(tài)修改*methodLists的值來(lái)添加成員方法,這也是Category實(shí)現(xiàn)的原理,同樣解釋了Category不能添加屬性的原因。關(guān)于二級(jí)指針,可以參考這篇文章。而最新版的 Runtime 源碼對(duì)這一塊的描述已經(jīng)有很大變化,可以參考下美團(tuán)技術(shù)團(tuán)隊(duì)的深入理解Objective-C:Category。
PS:任性的話可以在Category中添加@dynamic的屬性,并利用運(yùn)行期動(dòng)態(tài)提供存取方法或干脆動(dòng)態(tài)轉(zhuǎn)發(fā);或者干脆使用關(guān)聯(lián)度對(duì)象(AssociatedObject)
其中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;

}

如果你C語(yǔ)言不是特別好,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類的單個(gè)成員變量的信息;同理objc_method_list結(jié)構(gòu)體存儲(chǔ)著objc_method數(shù)組列表,而objc_method結(jié)構(gòu)體存儲(chǔ)了類的某個(gè)方法的信息。
最后要提到的還有一個(gè)objc_cache,顧名思義它是緩存,它在objc_class的作用很重要,在后面會(huì)講到。
不知道你是否注意到了objc_class中也有一個(gè)isa對(duì)象,這是因?yàn)橐粋€(gè) ObjC 類本身同時(shí)也是一個(gè)對(duì)象,為了處理類和對(duì)象的關(guān)系,runtime 庫(kù)創(chuàng)建了一種叫做元類 (Meta Class) 的東西,類對(duì)象所屬類型就叫做元類,它用來(lái)表述類對(duì)象本身所具備的元數(shù)據(jù)。類方法就定義于此處,因?yàn)檫@些方法可以理解成類對(duì)象的實(shí)例方法。每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類。當(dāng)你發(fā)出一個(gè)類似[NSObject alloc]的消息時(shí),你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類對(duì)象 (Class Object) ,這個(gè)類對(duì)象必須是一個(gè)元類的實(shí)例,而這個(gè)元類同時(shí)也是一個(gè)根元類 (root meta class) 的實(shí)例。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類對(duì)象的時(shí)候,objc_msgSend()會(huì)去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了,然后對(duì)這個(gè)類對(duì)象執(zhí)行方法調(diào)用。

上圖實(shí)線是 super_class 指針,虛線是isa指針。 有趣的是根元類的超類是NSObject,而isa指向了自己,而NSObject的超類為nil,也就是它沒有超類。
Method
Method是一種代表類中的某個(gè)方法的類型。
typedef struct objc_method *Method;

而objc_method在上面的方法列表中提到過(guò),它存儲(chǔ)了方法名,方法類型和方法實(shí)現(xiàn):
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

方法名類型為SEL,前面提到過(guò)相同名字的方法即使在不同類中定義,它們的方法選擇器也相同。
方法類型method_types是個(gè)char指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型。
method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)指針,后面會(huì)詳細(xì)講到。

Ivar
Ivar是一種代表類中實(shí)例變量的類型。
typedef struct objc_ivar *Ivar;

而objc_ivar在上面的成員變量列表中也提到過(guò):
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

} OBJC2_UNAVAILABLE;

可以根據(jù)實(shí)例查找其在類中的名字,也就是“反射”:
-(NSString *)nameWithInstance:(id)instance {
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
const char *type = ivar_getTypeEncoding(thisIvar);
NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
if (![stringType hasPrefix:@"@"]) {
continue;
}
if ((object_getIvar(self, thisIvar) == instance)) {//此處若 crash 不要慌!
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}

class_copyIvarList 函數(shù)獲取的不僅有實(shí)例變量,還有屬性。但會(huì)在原本的屬性名前加上一個(gè)下劃線。
IMP
IMP在objc.h中的定義是:
1

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

它就是一個(gè)函數(shù)指針,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) Objective-C 消息之后,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的。而 IMP 這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開消息傳遞階段,直接執(zhí)行方法,這在后面會(huì)提到。
你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類型相同,參數(shù)都包含id和SEL類型。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過(guò)一組id和SEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址;反之亦然。
Cache
在runtime.h中Cache的定義如下:
typedef struct objc_cache *Cache

還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache cache吧,它到底是緩存啥的呢,先看看objc_cache的實(shí)現(xiàn):
struct objc_cache {
unsigned int mask /
total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};

Cache為方法調(diào)用的性能進(jìn)行優(yōu)化,通俗地講,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí),它不會(huì)直接在isa指向的類的方法列表中遍歷查找能夠響應(yīng)消息的方法,因?yàn)檫@樣效率太低了,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過(guò)的 CPU 繞過(guò)主存先訪問(wèn)Cache的道理挺像,而我猜蘋果為提高Cache命中率應(yīng)該也做了努力吧。
Property
@property標(biāo)記了類中的屬性,這個(gè)不必多說(shuō)大家都很熟悉,它是一個(gè)指向objc_property結(jié)構(gòu)體的指針:
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個(gè)更常用

可以通過(guò)class_copyPropertyList 和 protocol_copyPropertyList方法來(lái)獲取類和協(xié)議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回類型為指向指針的指針,哈哈,因?yàn)閷傩粤斜硎莻€(gè)數(shù)組,每個(gè)元素內(nèi)容都是一個(gè)objc_property_t指針,而這兩個(gè)函數(shù)返回的值是指向這個(gè)數(shù)組的指針。
舉個(gè)例子,先聲明一個(gè)類:
@interface Lender : NSObject
@property float alone;
@end

你可以用property_getName函數(shù)來(lái)查找屬性名稱:
const char *property_getName(objc_property_t property)

你可以用class_getProperty 和 protocol_getProperty通過(guò)給出的名稱來(lái)在類和協(xié)議中獲取屬性的引用:
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

你可以用property_getAttributes函數(shù)來(lái)發(fā)掘?qū)傩缘拿Q和@encode類型字符串:
const char *property_getAttributes(objc_property_t property)

把上面的代碼放一起,你就能從一個(gè)類中獲取它的屬性啦:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}

對(duì)比下 class_copyIvarList 函數(shù),使用 class_copyPropertyList 函數(shù)只能獲取類的屬性,而不包含成員變量。但此時(shí)獲取的屬性名是不帶下劃線的。
消息
前面做了這么多鋪墊,現(xiàn)在終于說(shuō)到了消息了。Objc 中發(fā)送消息是用中括號(hào)([])把接收者和消息括起來(lái),而直到運(yùn)行時(shí)才會(huì)把消息與方法實(shí)現(xiàn)綁定。
有關(guān)消息發(fā)送和消息轉(zhuǎn)發(fā)機(jī)制的原理,可以查看****這篇文章****。
objc_msgSend函數(shù)
在引言中已經(jīng)對(duì)objc_msgSend進(jìn)行了一點(diǎn)介紹,看起來(lái)像是objc_msgSend返回了數(shù)據(jù),其實(shí)objc_msgSend從不返回?cái)?shù)據(jù)而是你的方法被調(diào)用后返回了數(shù)據(jù)。下面詳細(xì)敘述下消息發(fā)送步驟:
檢測(cè)這個(gè) selector 是不是要忽略的。比如 Mac OS X 開發(fā),有了垃圾回收就不理會(huì) retain, release 這些函數(shù)了。
檢測(cè)這個(gè) target 是不是 nil 對(duì)象。ObjC 的特性是允許對(duì)一個(gè) nil 對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash,因?yàn)闀?huì)被忽略掉。
如果上面兩個(gè)都過(guò)了,那就開始查找這個(gè)類的 IMP,先從 cache 里面找,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行。
如果 cache 找不到就找一下方法分發(fā)表。
如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找,直到找到NSObject類為止。
如果還找不到就要開始進(jìn)入動(dòng)態(tài)方法解析了,后面會(huì)提到。

PS:這里說(shuō)的分發(fā)表其實(shí)就是Class中的方法列表,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)。

其實(shí)編譯器會(huì)根據(jù)情況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或objc_msgSendSuper_stret四個(gè)方法中選擇一個(gè)來(lái)調(diào)用。如果消息是傳遞給超類,那么會(huì)調(diào)用名字帶有”Super”的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡(jiǎn)單值時(shí),那么會(huì)調(diào)用名字帶有”stret”的函數(shù)。排列組合正好四個(gè)方法。
值得一提的是在 i386 平臺(tái)處理返回類型為浮點(diǎn)數(shù)的消息時(shí),需要用到objc_msgSend_fpret函數(shù)來(lái)進(jìn)行處理,這是因?yàn)榉祷仡愋蜑楦↑c(diǎn)數(shù)的函數(shù)對(duì)應(yīng)的 ABI(Application Binary Interface) 與返回整型的函數(shù)的 ABI 不兼容。此時(shí)objc_msgSend不再適用,于是objc_msgSend_fpret被派上用場(chǎng),它會(huì)對(duì)浮點(diǎn)數(shù)寄存器做特殊處理。不過(guò)在 PPC 或 PPC64 平臺(tái)是不需要麻煩它的。
PS:有木有發(fā)現(xiàn)這些函數(shù)的命名規(guī)律哦?帶“Super”的是消息傳遞給超類;“stret”可分為“st”+“ret”兩部分,分別代表“struct”和“return”;“fpret”就是“fp”+“ret”,分別代表“floating-point”和“return”。
方法中的隱藏參數(shù)
我們經(jīng)常在方法中使用self關(guān)鍵字來(lái)引用實(shí)例本身,但從沒有想過(guò)為什么self就能取到調(diào)用當(dāng)前方法的對(duì)象吧。其實(shí)self的內(nèi)容是在方法運(yùn)行時(shí)被偷偷的動(dòng)態(tài)傳入的。
當(dāng)objc_msgSend找到方法對(duì)應(yīng)的實(shí)現(xiàn)時(shí),它將直接調(diào)用該方法實(shí)現(xiàn),并將消息中所有的參數(shù)都傳遞給方法實(shí)現(xiàn),同時(shí),它還將傳遞兩個(gè)隱藏的參數(shù):
接收消息的對(duì)象(也就是self指向的內(nèi)容)
方法選擇器(_cmd指向的內(nèi)容)

之所以說(shuō)它們是隱藏的是因?yàn)樵谠创a方法的定義中并沒有聲明這兩個(gè)參數(shù)。它們是在代碼被編譯時(shí)被插入實(shí)現(xiàn)中的。盡管這些參數(shù)沒有被明確聲明,在源代碼中我們?nèi)匀豢梢砸盟鼈儭T谙旅娴睦又校瑂elf引用了接收者對(duì)象,而_cmd引用了方法本身的選擇器:

  • strange
    {
    id target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
    return nil;
    return [target performSelector:method];
    }

在這兩個(gè)參數(shù)中,self 更有用。實(shí)際上,它是在方法實(shí)現(xiàn)中訪問(wèn)消息接收者對(duì)象的實(shí)例變量的途徑。
而當(dāng)方法中的super關(guān)鍵字接收到消息時(shí),編譯器會(huì)創(chuàng)建一個(gè)objc_super結(jié)構(gòu)體:
struct objc_super { id receiver; Class class; };

這個(gè)結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定超類的定義。但receiver仍然是self本身,這點(diǎn)需要注意,因?yàn)楫?dāng)我們想通過(guò)[super class]獲取超類時(shí),編譯器只是將指向self的id指針和class的SEL傳遞給了objc_msgSendSuper函數(shù),因?yàn)橹挥性贜SObject類才能找到class方法,然后class方法調(diào)用object_getClass(),接著調(diào)用objc_msgSend(objc_super->receiver, @selector(class)),傳入的第一個(gè)參數(shù)是指向self的id指針,與調(diào)用[self class]相同,所以我們得到的永遠(yuǎn)都是self的類型。
獲取方法地址
在IMP那節(jié)提到過(guò)可以避開消息綁定而直接獲取方法的地址并調(diào)用方法。這種做法很少用,除非是需要持續(xù)大量重復(fù)調(diào)用某方法的極端情況,避開消息發(fā)送泛濫而直接調(diào)用該方法會(huì)更高效。
NSObject類中有個(gè)methodForSelector:實(shí)例方法,你可以用它來(lái)獲取某個(gè)方法選擇器對(duì)應(yīng)的IMP,舉個(gè)例子:
void (*setter)(id, SEL, BOOL);
int i;

setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);

當(dāng)方法被當(dāng)做函數(shù)調(diào)用時(shí),上節(jié)提到的兩個(gè)隱藏參數(shù)就需要我們明確給出了。上面的例子調(diào)用了1000次函數(shù),你可以試試直接給target發(fā)送1000次setFilled:消息會(huì)花多久。
PS:methodForSelector:方法是由 Cocoa 的 Runtime 系統(tǒng)提供的,而不是 Objc 自身的特性。
動(dòng)態(tài)方法解析
你可以動(dòng)態(tài)地提供一個(gè)方法的實(shí)現(xiàn)。例如我們可以用@dynamic關(guān)鍵字在類的實(shí)現(xiàn)文件中修飾一個(gè)屬性:
@dynamic propertyName;

這表明我們會(huì)為這個(gè)屬性動(dòng)態(tài)提供存取方法,也就是說(shuō)編譯器不會(huì)再默認(rèn)為我們生成setPropertyName:和propertyName方法,而需要我們動(dòng)態(tài)提供。我們可以通過(guò)分別重載resolveInstanceMethod:和resolveClassMethod:方法分別添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn)。因?yàn)楫?dāng) Runtime 系統(tǒng)在Cache和方法分發(fā)表中(包括超類)找不到要執(zhí)行的方法時(shí),Runtime會(huì)調(diào)用resolveInstanceMethod:或resolveClassMethod:來(lái)給程序員一次動(dòng)態(tài)添加方法實(shí)現(xiàn)的機(jī)會(huì)。我們需要用class_addMethod函數(shù)完成向特定類添加特定方法實(shí)現(xiàn)的操作:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
@implementation MyClass

  • (BOOL)resolveInstanceMethod:(SEL)aSEL
    {
    if (aSEL == @selector(resolveThisMethodDynamically)) {
    class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
    return YES;
    }
    return [super resolveInstanceMethod:aSEL];
    }
    @end

上面的例子為resolveThisMethodDynamically方法添加了實(shí)現(xiàn)內(nèi)容,也就是dynamicMethodIMP方法中的代碼。其中 “v@:” 表示返回值和參數(shù),這個(gè)符號(hào)涉及 Type Encoding
PS:動(dòng)態(tài)方法解析會(huì)在消息轉(zhuǎn)發(fā)機(jī)制浸入前執(zhí)行。如果 respondsToSelector: 或 instancesRespondToSelector:方法被執(zhí)行,動(dòng)態(tài)方法解析器將會(huì)被首先給予一個(gè)提供該方法選擇器對(duì)應(yīng)的IMP的機(jī)會(huì)。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制,那么就讓resolveInstanceMethod:返回NO。
評(píng)論區(qū)有人問(wèn)如何用 resolveClassMethod: 解析類方法,我將他貼出有問(wèn)題的代碼做了糾正和優(yōu)化后如下,可以順便將實(shí)例方法和類方法的動(dòng)態(tài)方法解析對(duì)比下:
頭文件:

import <Foundation/Foundation.h>

@interface Student : NSObject

  • (void)learnClass:(NSString *) string;
  • (void)goToSchool:(NSString *) name;
    @end

m 文件:

import "Student.h"

import <objc/runtime.h>

@implementation Student

  • (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(learnClass:)) {
    class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
    return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
    }

  • (BOOL)resolveInstanceMethod:(SEL)aSEL
    {
    if (aSEL == @selector(goToSchool:)) {
    class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
    return YES;
    }
    return [super resolveInstanceMethod:aSEL];
    }

  • (void)myClassMethod:(NSString *)string {
    NSLog(@"myClassMethod = %@", string);
    }

  • (void)myInstanceMethod:(NSString *)string {
    NSLog(@"myInstanceMethod = %@", string);
    }
    @end

需要深刻理解 [self class] 與 object_getClass(self) 甚至 object_getClass([self class]) 的關(guān)系,其實(shí)并不難,重點(diǎn)在于 self 的類型:
當(dāng) self 為實(shí)例對(duì)象時(shí),[self class] 與 object_getClass(self) 不等價(jià),因?yàn)榍罢邥?huì)調(diào)用后者。object_getClass([self class]) 得到元類。
當(dāng) self 為類對(duì)象時(shí),[self class] 返回值為自身,還是 self。object_getClass(self) 與object_getClass([self class]) 等價(jià)。

凡是涉及到類方法時(shí),一定要弄清楚元類、selector、IMP 等概念,這樣才能做到舉一反三,隨機(jī)應(yīng)變。
消息轉(zhuǎn)發(fā)

重定向
在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì),即通過(guò)重載- (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對(duì)象:

  • (id)forwardingTargetForSelector:(SEL)aSelector
    {
    if(aSelector == @selector(mysteriousMethod:)){
    return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
    }

畢竟消息轉(zhuǎn)發(fā)要耗費(fèi)更多時(shí)間,抓住這次機(jī)會(huì)將消息重定向給別人是個(gè)不錯(cuò)的選擇,不過(guò)千萬(wàn)別返回self,因?yàn)槟菢訒?huì)死循環(huán)。 如果此方法返回nil或self,則會(huì)進(jìn)入消息轉(zhuǎn)發(fā)機(jī)制(forwardInvocation:);否則將向返回的對(duì)象重新發(fā)送消息。
轉(zhuǎn)發(fā)
當(dāng)動(dòng)態(tài)方法解析不作處理返回NO時(shí),消息轉(zhuǎn)發(fā)機(jī)制會(huì)被觸發(fā)。在這時(shí)forwardInvocation:方法會(huì)被執(zhí)行,我們可以重寫這個(gè)方法來(lái)定義我們的轉(zhuǎn)發(fā)邏輯:

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

該消息的唯一參數(shù)是個(gè)NSInvocation類型的對(duì)象——該對(duì)象封裝了原始的消息和消息的參數(shù)。我們可以實(shí)現(xiàn)forwardInvocation:方法來(lái)對(duì)不能處理的消息做一些默認(rèn)的處理,也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象來(lái)處理,而不拋出錯(cuò)誤。
這里需要注意的是參數(shù)anInvocation是從哪的來(lái)的呢?其實(shí)在forwardInvocation:消息發(fā)送前,Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對(duì)象。所以我們?cè)谥貙慺orwardInvocation:的同時(shí)也要重寫methodSignatureForSelector:方法,否則會(huì)拋異常。
當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過(guò)forwardInvocation:消息通知該對(duì)象。每個(gè)對(duì)象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了doesNotRecognizeSelector:。通過(guò)實(shí)現(xiàn)我們自己的forwardInvocation:方法,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。
forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤。forwardInvocation:方法也可以對(duì)不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力。
注意: forwardInvocation:方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用。 所以,如果我們希望一個(gè)對(duì)象將negotiate消息轉(zhuǎn)發(fā)給其它對(duì)象,則這個(gè)對(duì)象不能健壯的實(shí)例變量
StartFragmentEndFragment StartFragmentEndFragment StartFragmentEndFragment StartFragmentEndFragment StartFragmentEndFragment
轉(zhuǎn)發(fā)和多繼承
轉(zhuǎn)發(fā)和繼承相似,可以用于為Objc編程添加一些多繼承的效果。就像下圖那樣,一個(gè)對(duì)象把消息轉(zhuǎn)發(fā)出去,就好似它把另一個(gè)對(duì)象中的方法借過(guò)來(lái)或是“繼承”過(guò)來(lái)一樣。

這使得不同繼承體系分支下的兩個(gè)類可以“繼承”對(duì)方的方法,在上圖中Warrior和Diplomat沒有繼承關(guān)系,但是Warrior將negotiate消息轉(zhuǎn)發(fā)給了Diplomat后,就好似Diplomat是Warrior的超類一樣。
消息轉(zhuǎn)發(fā)彌補(bǔ)了 Objc 不支持多繼承的性質(zhì),也避免了因?yàn)槎嗬^承導(dǎo)致單個(gè)類變得臃腫復(fù)雜。它將問(wèn)題分解得很細(xì),只針對(duì)想要借鑒的方法才轉(zhuǎn)發(fā),而且轉(zhuǎn)發(fā)機(jī)制是透明的。
替代者對(duì)象(Surrogate Objects)
轉(zhuǎn)發(fā)不僅能模擬多繼承,也能使輕量級(jí)對(duì)象代表重量級(jí)對(duì)象。弱小的女人背后是強(qiáng)大的男人,畢竟女人遇到難題都把它們轉(zhuǎn)發(fā)給男人來(lái)做了。這里有一些適用案例,可以參看官方文檔。
轉(zhuǎn)發(fā)與繼承
盡管轉(zhuǎn)發(fā)很像繼承,但是NSObject類不會(huì)將兩者混淆。像respondsToSelector: 和 isKindOfClass:這類方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈。比如上圖中一個(gè)Warrior對(duì)象如果被問(wèn)到是否能響應(yīng)negotiate消息:
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...

結(jié)果是NO,盡管它能夠接受negotiate消息而不報(bào)錯(cuò),因?yàn)樗哭D(zhuǎn)發(fā)消息給Diplomat類來(lái)響應(yīng)消息。
如果你為了某些意圖偏要“弄虛作假”讓別人以為Warrior繼承到了Diplomat的negotiate方法,你得重新實(shí)現(xiàn)respondsToSelector: 和 isKindOfClass:來(lái)加入你的轉(zhuǎn)發(fā)算法:

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

除了respondsToSelector: 和 isKindOfClass:之外,instancesRespondToSelector:中也應(yīng)該寫一份轉(zhuǎn)發(fā)算法。如果使用了協(xié)議,conformsToProtocol:同樣也要加入到這一行列中。類似地,如果一個(gè)對(duì)象轉(zhuǎn)發(fā)它接受的任何遠(yuǎn)程消息,它得給出一個(gè)methodSignatureForSelector:來(lái)返回準(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;
    }

健壯的實(shí)例變量(Non Fragile ivars)
在 Runtime 的現(xiàn)行版本中,最大的特點(diǎn)就是健壯的實(shí)例變量。當(dāng)一個(gè)類被編譯時(shí),實(shí)例變量的布局也就形成了,它表明訪問(wèn)類的實(shí)例變量的位置。從對(duì)象頭部開始,實(shí)例變量依次根據(jù)自己所占空間而產(chǎn)生位移:

上圖左邊是NSObject類的實(shí)例變量布局,右邊是我們寫的類的布局,也就是在超類后面加上我們自己類的實(shí)例變量,看起來(lái)不錯(cuò)。但試想如果哪天蘋果更新了NSObject類,發(fā)布新版本的系統(tǒng)的話,那就悲劇了:

我們自定義的類被劃了兩道線,那是因?yàn)槟菈K區(qū)域跟超類重疊了。唯有蘋果將超類改為以前的布局才能拯救我們,但這樣也導(dǎo)致它們不能再拓展它們的框架了,因?yàn)槌蓡T變量布局被死死地固定了。在脆弱的實(shí)例變量(Fragile ivars) 環(huán)境下我們需要重新編譯繼承自 Apple 的類來(lái)恢復(fù)兼容性。那么在健壯的實(shí)例變量下會(huì)發(fā)生什么呢?

在健壯的實(shí)例變量下編譯器生成的實(shí)例變量布局跟以前一樣,但是當(dāng) runtime 系統(tǒng)檢測(cè)到與超類有部分重疊時(shí)它會(huì)調(diào)整你新添加的實(shí)例變量的位移,那樣你在子類中新添加的成員就被保護(hù)起來(lái)了。
需要注意的是在健壯的實(shí)例變量下,不要使用sizeof(SomeClass),而是用class_getInstanceSize([SomeClass class])代替;也不要使用offsetof(SomeClass, SomeIvar),而要用ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar"))來(lái)代替。
Objective-C Associated Objects
在 OS X 10.6 之后,Runtime系統(tǒng)讓Objc支持向?qū)ο髣?dòng)態(tài)添加變量。涉及到的函數(shù)有以下三個(gè):
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
id objc_getAssociatedObject ( id object, const void *key );
void objc_removeAssociatedObjects ( id object );

這些方法以鍵值對(duì)的形式動(dòng)態(tài)地向?qū)ο筇砑印@取或刪除關(guān)聯(lián)值。其中關(guān)聯(lián)政策是一組枚舉常量:
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};

這些常量對(duì)應(yīng)著引用關(guān)聯(lián)值的政策,也就是 Objc 內(nèi)存管理的引用計(jì)數(shù)機(jī)制。有關(guān) Objective-C 引用計(jì)數(shù)機(jī)制的原理,可以查看****這篇文章
Method Swizzling
之前所說(shuō)的消息轉(zhuǎn)發(fā)雖然功能強(qiáng)大,但需要我們了解并且能更改對(duì)應(yīng)類的源代碼,因?yàn)槲覀冃枰獙?shí)現(xiàn)自己的轉(zhuǎn)發(fā)邏輯。當(dāng)我們無(wú)法觸碰到某個(gè)類的源代碼,卻想更改這個(gè)類某個(gè)方法的實(shí)現(xiàn)時(shí),該怎么辦呢?可能繼承類并重寫方法是一種想法,但是有時(shí)無(wú)法達(dá)到目的。這里介紹的是 Method Swizzling ,它通過(guò)重新映射方法對(duì)應(yīng)的實(shí)現(xiàn)來(lái)達(dá)到“偷天換日”的目的。跟消息轉(zhuǎn)發(fā)相比,Method Swizzling 的做法更為隱蔽,甚至有些冒險(xiǎn),也增大了debug的難度。
這里摘抄一個(gè) NSHipster 的例子:

import <objc/runtime.h>

@implementation UIViewController (Tracking)

  • (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    Class aClass = [self class];

      SEL originalSelector = @selector(viewWillAppear:); 
      SEL swizzledSelector = @selector(xxx_viewWillAppear:); 
    
      Method originalMethod = class_getInstanceMethod(aClass, originalSelector); 
      Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); 
      
      // When swizzling a class method, use the following:
      // Class aClass = object_getClass((id)self);
      // ...
      // Method originalMethod = class_getClassMethod(aClass, originalSelector);
      // Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);
    
      BOOL didAddMethod = 
          class_addMethod(aClass, 
              originalSelector, 
              method_getImplementation(swizzledMethod), 
              method_getTypeEncoding(swizzledMethod)); 
    
      if (didAddMethod) { 
          class_replaceMethod(aClass, 
              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

上面的代碼通過(guò)添加一個(gè)Tracking類別到UIViewController類中,將UIViewController類的viewWillAppear:方法和Tracking類別中xxx_viewWillAppear:方法的實(shí)現(xiàn)相互調(diào)換。Swizzling 應(yīng)該在+load方法中實(shí)現(xiàn),因?yàn)?load是在一個(gè)類最開始加載時(shí)調(diào)用。dispatch_once是GCD中的一個(gè)方法,它保證了代碼塊只執(zhí)行一次,并讓其為一個(gè)原子操作,線程安全是很重要的。
如果類中不存在要替換的方法,那就先用class_addMethod和class_replaceMethod函數(shù)添加和替換兩個(gè)方法的實(shí)現(xiàn);如果類中已經(jīng)有了想要替換的方法,那么就調(diào)用method_exchangeImplementations函數(shù)交換了兩個(gè)方法的 IMP,這是蘋果提供給我們用于實(shí)現(xiàn) Method Swizzling 的便捷方法。
可能有人注意到了這行:
1
2
3
4
5

// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);

object_getClass((id)self) 與 [self class] 返回的結(jié)果類型都是 Class,但前者為元類,后者為其本身,因?yàn)榇藭r(shí)self 為 Class 而不是實(shí)例.注意 [NSObject class] 與 [object class] 的區(qū)別:

  • (Class)class {
    return self;
    }
  • (Class)class {
    return object_getClass(self);
    }

PS:如果類中沒有想被替換實(shí)現(xiàn)的原方法時(shí),class_replaceMethod相當(dāng)于直接調(diào)用class_addMethod向類中添加該方法的實(shí)現(xiàn);否則調(diào)用method_setImplementation方法,types參數(shù)會(huì)被忽略。method_exchangeImplementations方法做的事情與如下的原子操作等價(jià):
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

最后xxx_viewWillAppear:方法的定義看似是遞歸調(diào)用引發(fā)死循環(huán),其實(shí)不會(huì)的。因?yàn)閇self xxx_viewWillAppear:animated]消息會(huì)動(dòng)態(tài)找到xxx_viewWillAppear:方法的實(shí)現(xiàn),而它的實(shí)現(xiàn)已經(jīng)被我們與viewWillAppear:方法實(shí)現(xiàn)進(jìn)行了互換,所以這段代碼不僅不會(huì)死循環(huán),如果你把[self xxx_viewWillAppear:animated]換成[self viewWillAppear:animated]反而會(huì)引發(fā)死循環(huán)。
看到有人說(shuō)+load方法本身就是線程安全的,因?yàn)樗诔绦騽傞_始就被調(diào)用,很少會(huì)碰到并發(fā)問(wèn)題,于是 stackoverflow 上也有大神給出了另一個(gè) Method Swizzling 的實(shí)現(xiàn):

  • (void)replacementReceiveMessage:(const struct BInstantMessage *)arg1 {
    NSLog(@"arg1 is %@", arg1);
    [self replacementReceiveMessage:arg1];
    }
  • (void)load {
    SEL originalSelector = @selector(ReceiveMessage:);
    SEL overrideSelector = @selector(replacementReceiveMessage:);
    Method originalMethod = class_getInstanceMethod(self, originalSelector);
    Method overrideMethod = class_getInstanceMethod(self, overrideSelector);
    if (class_addMethod(self, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
    class_replaceMethod(self, overrideSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
    method_exchangeImplementations(originalMethod, overrideMethod);
    }
    }

上面的代碼同樣要添加在某個(gè)類的類別中,相比第一個(gè)種實(shí)現(xiàn),只是去掉了dispatch_once部分。
Method Swizzling 的確是一個(gè)值得深入研究的話題,Method Swizzling 的最佳實(shí)現(xiàn)是什么呢?小弟才疏學(xué)淺理解的不深刻,找了幾篇不錯(cuò)的資源推薦給大家:
Objective-C的hook方案(一): Method Swizzling
Method Swizzling
How do I implement method swizzling?
What are the Dangers of Method Swizzling in Objective C?
JRSwizzle

在用 SpriteKit 寫游戲的時(shí)候,因?yàn)?API 本身有一些缺陷(增刪節(jié)點(diǎn)時(shí)不考慮父節(jié)點(diǎn)是否存在啊,很容易崩潰啊有木有!),我在 Swift 上使用 Method Swizzling彌補(bǔ)這個(gè)缺陷:
extension SKNode {

class func yxy_swizzleAddChild() {
    let cls = SKNode.self
    let originalSelector = Selector("addChild:")
    let swizzledSelector = Selector("yxy_addChild:")
    let originalMethod = class_getInstanceMethod(cls, originalSelector)
    let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

class func yxy_swizzleRemoveFromParent() {
    let cls = SKNode.self
    let originalSelector = Selector("removeFromParent")
    let swizzledSelector = Selector("yxy_removeFromParent")
    let originalMethod = class_getInstanceMethod(cls, originalSelector)
    let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
    method_exchangeImplementations(originalMethod, swizzledMethod)
}

func yxy_addChild(node: SKNode) {
    if node.parent == nil {
        self.yxy_addChild(node)
    }
    else {
        println("This node has already a parent!\(node.name)")
    }
}

func yxy_removeFromParent() {
    if parent != nil {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.yxy_removeFromParent()
        })
    }
    else {
        println("This node has no parent!\(name)")
    }
}

}

然后其他地方調(diào)用那兩個(gè)類方法:
SKNode.yxy_swizzleAddChild()
SKNode.yxy_swizzleRemoveFromParent()

因?yàn)?Swift 中的 extension 的特殊性,最好在某個(gè)類的load() 方法中調(diào)用上面的兩個(gè)方法.我是在AppDelegate 中調(diào)用的,于是保證了應(yīng)用啟動(dòng)時(shí)能夠執(zhí)行上面兩個(gè)方法.
總結(jié)
我們之所以讓自己的類繼承NSObject不僅僅因?yàn)樘O果幫我們完成了復(fù)雜的內(nèi)存分配問(wèn)題,更是因?yàn)檫@使得我們能夠用上 Runtime 系統(tǒng)帶來(lái)的便利。可能我們平時(shí)寫代碼時(shí)可能很少會(huì)考慮一句簡(jiǎn)單的[receiver message]背后發(fā)生了什么,而只是當(dāng)做方法或函數(shù)調(diào)用。深入理解 Runtime 系統(tǒng)的細(xì)節(jié)更有利于我們利用消息機(jī)制寫出功能更強(qiáng)大的代碼,比如 Method Swizzling 等。
參考鏈接:
Objective-C Runtime Programming Guide
Objective-C runtime之運(yùn)行時(shí)的基本特點(diǎn)
Understanding the Objective-C Runtime

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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