目錄
- 前言
- iOS編譯流程
- runtime介紹
- 消息發(fā)送流程
- 消息轉(zhuǎn)發(fā)流程
- Method Swizzling
- 參考文檔
前言
關(guān)于runtime的文章, 網(wǎng)上實(shí)在太多了, 內(nèi)容層次深淺不一. 誠然, 要想把runtime討論明白, 講得深入徹底, 沒有相當(dāng)功力是不行的. 故, 本文退而求其次, 希望能把runtime講得"知其然", 想必也是挺好的.
iOS編譯流程
我們編寫的所有代碼, 最終都是要轉(zhuǎn)換成二進(jìn)制機(jī)器指令去執(zhí)行的. 如圖
C/C++/OC編譯流程:
Swift編譯流程:
注:
例圖是以模擬器為例, 所以編譯結(jié)果為 x86-64 CPU 機(jī)器語言; 如果是真機(jī), 則最終編譯成 ARM CPU 機(jī)器語言
解析
- iOS 開發(fā)中 Objective-C 是 Clang / LLVM 來編譯的。
- Swift 是 Swift / LLVM惹挟,其中 Swift 前端會多出 SIL optimizer垄惧,它會把 .swift 生成的中間代碼 .sil 屬于 High-Level IR, 因?yàn)?swift 在編譯時(shí)就完成了方法綁定直接通過地址調(diào)用屬于強(qiáng)類型語言嘹害,方法調(diào)用不再是像OC那樣的消息發(fā)送撮竿,這樣編譯就可以獲得更多的信息用在后面的后端優(yōu)化上。
- 不管編譯的語言時(shí) Objective-C 還是 Swift 也不管對應(yīng)機(jī)器是什么笔呀,亦或是即時(shí)編譯幢踏,LLVM 里唯一不變的是中間語言 LLVM IR。
引申
高級編程語言想要成為可執(zhí)行文件需要先編譯為匯編語言再匯編為機(jī)器語言许师,但是OC并不能直接編譯為匯編語言房蝉,而是要先轉(zhuǎn)寫為純C語言再進(jìn)行編譯和匯編的操作,從OC到C語言的過渡就是由runtime來實(shí)現(xiàn)的微渠。
關(guān)于iOS中的匯編, 詳見深入iOS系統(tǒng)底層之匯編語言.
結(jié)論
在整個(gè)iOS編譯過程中, runtime處在LLVM的前端部分(Frontend). 更具體點(diǎn), 可以說是runtime將OC轉(zhuǎn)換成C.
runtime介紹
為何要有runtime
-
C
: 靜態(tài)語言. 編譯階段就要決定調(diào)用哪個(gè)函數(shù), 如果函數(shù)未實(shí)現(xiàn)就會編譯報(bào)錯(cuò). -
OC
: 動態(tài)語言(得益于runtime機(jī)制). 運(yùn)行時(shí)才決定調(diào)用哪個(gè)函數(shù), 只要函數(shù)聲明過即使沒有實(shí)現(xiàn)也不會報(bào)錯(cuò). -
Swift
: 靜態(tài)語言. 其對象方法的調(diào)用基本上是在編譯鏈接時(shí)刻就被確定的. 詳見Swift5.0的Runtime機(jī)制淺析.
Swift基本上取消了runtime機(jī)制, 故本文還是主要討論OC下的runtime. 當(dāng)然, 通過Swift與OC混編, 我們也可以在Swift文件中調(diào)用OC的runtime接口.
總所周知, OC 擴(kuò)展自 C 語言搭幻,然后擁有了面向?qū)ο笮再|(zhì)和消息傳遞機(jī)制, 成為了動態(tài)語言。而這個(gè)擴(kuò)展的核心就是我們今天的主角—— runtime逞盆。
何為runtime
runtime 其實(shí)是一個(gè)系統(tǒng)動態(tài)共享庫, 具有一個(gè)公共接口, 該公共接口由頭文件中的一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成 (純C語言API). 由于所有的OC代碼終將轉(zhuǎn)換成C代碼, 使得 runtime 的API調(diào)用非常頻繁, 所以新版runtime里面對應(yīng)的實(shí)現(xiàn)基本上都是用C++和匯編語言混合來寫的, 以便提高系統(tǒng)效率.
runtime原理
這一部分的討論, 將圍繞類
檀蹋、實(shí)例
令野、屬性
垄懂、方法
以及類別
等在runtime中的表現(xiàn)形式來展開.
1. id --> objc_object
id是一個(gè)指向類實(shí)例的指針, 它在runtime中的定義如下:
typedef struct objc_object *id;
而objc_object在objc-private.h中定義如下:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
... 內(nèi)容太多故省略
}
objc_object
結(jié)構(gòu)體包含一個(gè)isa
指針, 根據(jù)isa
就可以順藤摸瓜找到對象所屬的類. 而isa
的類型isa_t
使用union
實(shí)現(xiàn), 可能表示多種形態(tài), 既可以當(dāng)成是指針, 也可以存儲標(biāo)志位. 這是蘋果提出的Tagged Pointer類型對象的概念, 目的是為了減少內(nèi)存資源的浪費(fèi). 畢竟用 64 bit 存儲一個(gè)內(nèi)存地址顯然是種浪費(fèi).
Tagged Pointer類型的對象采用一個(gè)跟機(jī)器字長一樣長度的整數(shù)來表示一個(gè)OC對象,而為了跟普通OC對象區(qū)分開來匣吊,每個(gè)Tagged Pointer類型對象的最高位為1而普通的OC對象的最高位為0.
小結(jié): OC中的對象終將轉(zhuǎn)換成C中的結(jié)構(gòu)體objc_object.
2. Class --> objc_class
我們在Xcode中輸入基類NSObject, 然后 ?+單擊 這個(gè)NSObject, 查看它的定義:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
其中省略了#pragma clang部分, 有興趣可查閱這篇博文#pragma
可以看到, NSObject有且僅有一個(gè)Class類型的isa屬性, 也就是說, 一個(gè)類對象唯一保存的信息就是它的 Class 的地址. 由于OC中幾乎所有的類(NSProxy等除外)都直接或間接地繼承于NSObject類, 可以說, OC中的類都有一個(gè)isa屬性. 那么這個(gè)isa又是什么呢?
我們繼續(xù) ?+單擊 Class, 可以看到他在runtime中的定義:
typedef struct objc_class *Class;
此時(shí)我們發(fā)現(xiàn), Class在runtime中是一個(gè)指向objc_class結(jié)構(gòu)體的指針.
isa, 意思是is a, 這是一個(gè)...
繼續(xù)查看objc_class結(jié)構(gòu)體, 我們看到它在Xcode的runtime.h里定義如下:
/**
* objc1.0
*/
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
注意后面這個(gè)OBJC2_UNAVAILABLE
其實(shí)上面這個(gè)是兼容objc1.0
版本的定義, 而目前我們使用的是objc2.0
版本,2.0
中沒有暴露出bjc_class
所定義的詳細(xì)內(nèi)容.
你可以在https://opensource.apple.com/source/objc4/objc4-723/中下載和查看開源的最新版本的Runtime庫源代碼舅逸。Runtime庫的源代碼是用匯編和C++混合實(shí)現(xiàn)的桌肴,你可以在頭文件objc-runtime-new.h中看到關(guān)于struct objc_class
結(jié)構(gòu)的詳細(xì)定義。
/**
* objc2.0
*/
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
... 內(nèi)容太多故省略
}
objc_class結(jié)構(gòu)體中太多字段了所以這里省略掉. 其內(nèi)容主要有:包括類的名字堡赔、所繼承的基類识脆、類中定義的方法列表描述、屬性列表描述善已、實(shí)現(xiàn)的協(xié)議描述灼捂、定義的成員變量描述等等信息。如圖:
objc_class
繼承于objc_object
, 也就是說一個(gè)OC類本身同時(shí)也是一個(gè)對象. 既然說類也是對象, 那么類的類型是什么呢换团?這里就引出了另外一個(gè)概念 —— Meta Class (元類).
小結(jié): OC中的類終將轉(zhuǎn)換成C中的結(jié)構(gòu)體objc_class.
3. Meta Class 元類
為了處理類和對象的關(guān)系, runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西.
其實(shí)觀察objc_class
和objc_object
的定義, 會發(fā)現(xiàn)兩者本質(zhì)相同(都包含isa
指針), 只是objc_class
多了一些額外的字段. 這些字段包括了創(chuàng)建一個(gè)類實(shí)例所需的信息, 以及這些實(shí)例的方法等. 那么類的信息和類方法儲存在哪呢? 答案是在元類里.
我們來看下這張著名的圖:
小結(jié):
- 實(shí)例的isa指針指向類, 類的isa指針指向元類.
- 類所對應(yīng)的objc_class里儲存了實(shí)例的方法, 元類所對應(yīng)的objc_class里儲存了類方法.
- 元類的isa指針指向自己, 形成閉環(huán).
4. Ivar 成員變量 和 objc_property_t 屬性
Ivar
Ivar: instance variable
Ivar 代表類實(shí)例的變量或?qū)傩?帶下劃線"_"), 其在runtime中定義如下:
typedef struct ivar_t *Ivar;
ivar_t最終嵌套在objc_class里, 在objc-rentime-new.h中的結(jié)構(gòu)體層級關(guān)系如下:
ivar_t -> ivar_list_t -> class_ro_t -> class_rw_t -> class_data_bits_t -> objc_class
我們可以遍歷一個(gè)類的成員變量和屬性(加"_"):
// 打印成員變量列表
- (void)logIvarList {
unsigned int count;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i=0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"成員變量%d: %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
}
objc_property_t
@property 標(biāo)記了類中的屬性, 它是一個(gè)指向objc_property 結(jié)構(gòu)體的指針:
typedef struct property_t *objc_property_t;
遍歷屬性:
// 打印屬性列表
- (void)logPropertyList {
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"屬性%d: %@", i, [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
5. Method / SEL / IMP 方法
Method
Method在runtime中定義如下:
typedef struct objc_method *Method;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
SEL: selector
IMP: implementation
Method中存儲了這三樣?xùn)|西:
- SEL類型的方法名.
- char指針的方法類型, 指向存儲方法的參數(shù)類型和返回值類型.
- IMP類型的方法實(shí)現(xiàn)地址.
SEL
Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
SEL是一個(gè)方法選擇器, 在runtime中其實(shí)是一個(gè)C字符串, 用來表示方法名稱.
SEL sel = @selector(viewDidLoad);
NSLog(@"%s", sel);
// 打印: viewDidLoad
SEL是由編譯器在裝載類的時(shí)候自動生成的, 因此我們不能強(qiáng)制將一個(gè)C字符串轉(zhuǎn)化為SEL. 我們可以使用OC編譯器命令@selector()
或者runtime系統(tǒng)的sel_registerName
函數(shù)來獲得一個(gè) SEL 類型的方法選擇器. 例如:
SEL sel1 = @selector(viewWillAppear:);
SEL sel2 = sel_registerName("init");
不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的, 即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器. 比如, 同一個(gè)類中, 相同方法名不同參數(shù)類型也是會報(bào)錯(cuò)的:
// 同一個(gè)類中, 相同方法名不同參數(shù)類型, 報(bào)錯(cuò)
- (void)caculate(NSInteger)num;
- (void)caculate(CGFloat)num;
IMP
IMP在runtime中的定義如下:
/// A pointer to the function of a method implementation.
typedef void (IMP)(void / id, SEL, ... */ );
IMP是一個(gè)函數(shù)指針, 指向了方法實(shí)現(xiàn)的首地址.
這里要注意悉稠, IMP 指向的函數(shù)的前兩個(gè)參數(shù)是默認(rèn)參數(shù), id 和 SEL 艘包。這里的 SEL 好理解的猛,就是函數(shù)名。而 id 想虎,對于實(shí)例方法來說卦尊, self 保存了當(dāng)前對象的地址;對于類方法來說舌厨, self 保存了當(dāng)前對應(yīng)類對象的地址岂却。后面的省略號即是參數(shù)列表。
小結(jié):
Method
/ SEL
/ IMP
這三個(gè)概念之間關(guān)系: 在運(yùn)行時(shí), 類(Class)維護(hù)了一個(gè)消息分發(fā)列表來解決消息的正確發(fā)送. 每一個(gè)消息列表的入口是一個(gè)方法(Method), 這個(gè)方法映射了一對鍵值對, 其中鍵值是這個(gè)方法的名字 selector (SEL), 值是指向這個(gè)方法實(shí)現(xiàn)的函數(shù)指針 implementation (IMP).
6. Category
Category 為現(xiàn)有的類提供了拓展性, 它是 objc_category 結(jié)構(gòu)體的指針.
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
}
雖然在OC2中不是這樣定義, 但是都有這些內(nèi)容, 為簡潔起見, 姑且這樣分析討論吧.
其中包含對象方法列表、類方法列表躏哩、協(xié)議列表等. 從這里我們也可以看出, Category 支持添加對象方法署浩、類方法、協(xié)議, 但不能保存成員變量.
注意:在 Category 中是可以添加屬性的扫尺,但不會生成對應(yīng)的成員變量筋栋、 getter 和 setter 。因此正驻,調(diào)用 Category 中聲明的屬性時(shí)會報(bào)錯(cuò)弊攘。
關(guān)聯(lián)對象
我們可以通過關(guān)聯(lián)對象的方式來添加可用的屬性:
- (void)setXxx:(NSString *)xxx {
objc_setAssociatedObject(self, &xxx, xxx, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)xxx {
return objc_getAssociatedObject(self, &xxx);
}
消息發(fā)送流程
當(dāng)我們在OC中執(zhí)行一個(gè)方法:
[receiver message]
編譯器會編譯成運(yùn)行時(shí)的C代碼:
objc_msgSend(receiver, selector)
如果消息含有參數(shù), 則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
其實(shí)有四個(gè)消息發(fā)送方法:
objc_msgSend
,objc_msgSend_stret
,objc_msgSendSuper
和objc_msgSendSuper_stret
。如果消息是傳遞給超類拨拓,那么會調(diào)用名字帶有”Super”的函數(shù)肴颊;如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時(shí),那么會調(diào)用名字帶有”stret”的函數(shù)渣磷。“stret”可分為“st”+“ret”兩部分授瘦,分別代表“struct”和“return”醋界。
對于[receiver message], 在編譯階段確定了要向接收者 receiver 發(fā)送 message 這條消息,而 receive 將要如何響應(yīng)這條消息, 那就要看運(yùn)行時(shí)發(fā)生的情況來決定了.
以下是objc_msgSend消息發(fā)送流程:
- 檢測這個(gè)selector是不是要被忽略的提完。比如 Mac OS X 開發(fā)形纺,有了垃圾回收就不理會 retain, release 這些函數(shù)了。
- 檢測這個(gè)target對象是不是nil對象徒欣。(nil對象執(zhí)行任何一個(gè)方法都不會Crash逐样,因?yàn)闀缓雎缘簦?/li>
- 首先會根據(jù)target(objc_object)對象的isa指針獲取它所對應(yīng)的類(objc_class)。
- 查看緩存cache中是否存在方法打肝。 如果有脂新,則找到objc_method中的IMP類型(函數(shù)指針)的成員method_imp去找到實(shí)現(xiàn)內(nèi)容,并執(zhí)行; 如果沒有粗梭,那么到該類的方法表(methodLists)查找該方法争便,依次從后往前查找。
- 如果沒有在類(class)找到断医,再到父類(super_class)查找滞乙,直至根類。
- 一旦找到與選擇子(selector)名稱相符的方法鉴嗤,就跳至其實(shí)現(xiàn)代碼斩启。
- 如果沒有找到,就會執(zhí)行消息轉(zhuǎn)發(fā)(message forwarding)的第一步動態(tài)解析醉锅。
消息轉(zhuǎn)發(fā)流程
先來看看這張圖:
向不處理該消息的對象發(fā)送消息是錯(cuò)誤的. 但是, 在宣布錯(cuò)誤之前, 運(yùn)行時(shí)系統(tǒng)會給接收對象第二次處理消息的機(jī)會. 這個(gè)機(jī)會分三步走:
- 動態(tài)方法解析
- 接收者重定向
- 消息重定向
1. 動態(tài)方法解析
我們可以通過分別重載+resolveInstanceMethod:
和+resolveClassMethod:
方法, 分別添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn). 然后返回YES, 運(yùn)行時(shí)系統(tǒng)就會重新啟動一次消息發(fā)送的過程.
??
// Person.h
@interface Person : NSObject
+ (void)eat;
- (void)work;
@end
// Person.m
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
// 添加類方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(eat)){
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(kk_eat)), "v@");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
// 添加實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(work)){
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(kk_work)), "v@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 新的類方法
+ (void)kk_eat {
NSLog(@"%s", __func__);
}
// 新的實(shí)例方法
- (void)kk_work {
NSLog(@"%s", __func__);
}
@end
// 類方法
[Person eat];
// 對象方法
Person *person = [Person new];
[person work];
// --------------------------------------------------------------
打印:
+[Person kk_eat]
-[Person kk_work]
注: 添加的方法必須實(shí)現(xiàn), 否則報(bào)錯(cuò)! 比如上述代碼中的
kk_eat
與kk_work
必須實(shí)現(xiàn).
關(guān)于class_addMethod
的最后一個(gè)參數(shù)types
我們使用C函數(shù)來說明會比較好理解, 比如:
void eat(id self, SEL _cmd, NSString *str)
{
NSLog(@"%@", str);
}
那么types參數(shù)為"v @ : @“, 按順序分別表示:
-
v
: 返回值類型void, 若是i則表示int -
@
: 參數(shù)id(self) -
:
: SEL(_cmd) -
@
: id(str)
更多類型詳見Type Encodings
2. 接收者重定向
如果動態(tài)方法解析部分中, +resolveInstanceMethod:
和+resolveClassMethod:
都返回了NO, 則會分別調(diào)用重定向類方法+forwardingTargetForSelector:
和重定向?qū)嵗椒?code>-forwardingTargetForSelector:. 在這兩個(gè)方法中, 我們可以指定新的消息接收者, 但要注意的是新的接受者必須實(shí)現(xiàn)了該消息.
?? ??
新建一個(gè)類Alien.
// Alien.h
@interface Alien : NSObject
+ (void)eat;
- (void)work;
@end
// Alien.m
#import "Alien.h"
@implementation Alien
+ (void)eat {
NSLog(@"%s", __func__);
}
- (void)work {
NSLog(@"%s", __func__);
}
@end
在原來的Person類的+resolveInstanceMethod:
和+resolveClassMethod:
方法里返回NO. 然后重寫重定向類方法+forwardingTargetForSelector:
和重定向?qū)嵗椒?code>-forwardingTargetForSelector:.
#import "Person.h"
#import <objc/runtime.h>
#import "Alien.h"
@implementation Person
// 添加類方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(eat)){
return NO;
// class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(kk_eat)), "v@:");
// return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
// 添加實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(work)){
return NO;
// class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(kk_work)), "v@:");
// return YES;
}
return [super resolveInstanceMethod:sel];
}
// 重定向類方法:返回一個(gè)類
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(eat)) {
return [Alien class];
}
return [super forwardingTargetForSelector:aSelector];
}
// 重定向?qū)嵗椒ǎ悍祷匾粋€(gè)實(shí)例
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(work)) {
Alien *alien = [Alien new];
return alien;
}
return [super forwardingTargetForSelector:aSelector];
}
// 新的類方法
+ (void)kk_eat {
NSLog(@"%s", __func__);
}
// 新的實(shí)例方法
- (void)kk_work {
NSLog(@"%s", __func__);
}
打印的不是Person的方法, 而是Alien的方法:
log:
+[Alien eat]
-[Alien work]
3. 消息重定向
如果
對于類方法, +resolveClassMethod:
返回NO, +forwardingTargetForSelector:
返回nil;
對于實(shí)例方法, +resolveInstanceMethod:
返回NO, -forwardingTargetForSelector:
返回nil.
那么
進(jìn)入第三步也是最后一步 —— 消息重定向.
消息重定向又分為兩個(gè)小步驟:
- runtime系統(tǒng)會向?qū)ο蟀l(fā)送
-methodSignatureForSelector
消息, 并取到返回的方法簽名用于生成NSInvocation對象; - 將生成的NSInvocation對象作為參數(shù)調(diào)用
-forwardInvocation:
?? ?? ??
// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// ViewController沒有work方法, 將會走轉(zhuǎn)發(fā)
[self performSelector:@selector(work)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 返回YES兔簇,進(jìn)入下一步轉(zhuǎn)發(fā)
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 返回nil,進(jìn)入下一步轉(zhuǎn)發(fā)
}
// 返回一個(gè)NSInvocation對象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
if (!methodSignature) {
// 生成方法簽名
methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return methodSignature;
}
// 消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
// // 類方法
// if ([[Alien class] respondsToSelector:sel]) {
// [anInvocation invokeWithTarget:[Alien class]];
// }else{
// // 若無法響應(yīng), 則報(bào)錯(cuò): 找不到響應(yīng)方法
// [self doesNotRecognizeSelector:sel];
// }
// 實(shí)例方法
Alien *alien = [Alien new];
if ([alien respondsToSelector:sel]) {
[anInvocation invokeWithTarget:alien];
}else{
// 若無法響應(yīng), 則報(bào)錯(cuò): 找不到響應(yīng)方法
[self doesNotRecognizeSelector:sel];
}
}
log:
-[Alien work]
Method Swizzling
消息轉(zhuǎn)發(fā)雖然功能強(qiáng)大,但需要我們了解并且能更改對應(yīng)類的源代碼男韧,因?yàn)槲覀冃枰獙?shí)現(xiàn)自己的轉(zhuǎn)發(fā)邏輯朴摊。當(dāng)我們無法觸碰到某個(gè)類的源代碼,卻想更改這個(gè)類某個(gè)方法的實(shí)現(xiàn)時(shí)此虑,該怎么辦呢甚纲?可能繼承類并重寫方法是一種想法,但是有時(shí)無法達(dá)到目的朦前。這里介紹的是 Method Swizzling 介杆,它通過重新映射方法對應(yīng)的實(shí)現(xiàn)來達(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];
// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(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
參考文檔
Objective-C Runtime
深入解構(gòu)objc_msgSend函數(shù)的實(shí)現(xiàn)
Runtime-iOS運(yùn)行時(shí)基礎(chǔ)篇
iOS Runtime詳解
runtime開源