前言
運(yùn)行時(shí)可以說(shuō)是 Objective-C 這門(mén)語(yǔ)言的一個(gè)核心部分,看了許多博客囚戚,也反反復(fù)復(fù)看了多次源碼(蘋(píng)果開(kāi)源的runtime源碼),對(duì)于這方面研究的文章博客也比較多,對(duì)技術(shù)懷有敬畏之心倦始,工作一年跟三年的對(duì)字符串的理解都是不一樣的,還是再寫(xiě)寫(xiě)吧讼溺!如有誤歡迎指正楣号,如果看完覺(jué)得對(duì)你學(xué)習(xí)有幫助,關(guān)注一波幫忙點(diǎn)個(gè)喜歡??唄。
初識(shí) Runtime - 運(yùn)行時(shí)
runtime 簡(jiǎn)稱(chēng)運(yùn)行時(shí)炫狱。其是系統(tǒng)運(yùn)行的時(shí)候的一些機(jī)制藻懒,主要體現(xiàn)的是對(duì)象的消息機(jī)制。(讀文章思考方式:看完上面一句話(huà)你腦海中應(yīng)該有這個(gè)三個(gè)關(guān)鍵詞運(yùn)行時(shí)视译、OC中對(duì)象嬉荆、消息發(fā)送 。本文也就是圍繞著三個(gè)關(guān)鍵詞進(jìn)行的思考與闡述的)酷含。
- Objective-C是基于C語(yǔ)言加入了面向?qū)ο筇匦?(面向?qū)ο筘灤┱麄€(gè)操作系統(tǒng)鄙早,OC是重度對(duì)象語(yǔ)言) 和消息轉(zhuǎn)發(fā)機(jī)制 (消息分發(fā)) 的動(dòng)態(tài)語(yǔ)言
- Objective-C 是如何具有面向?qū)ο蟮哪芰Φ哪兀縊C中對(duì)象通過(guò) C 語(yǔ)言的結(jié)構(gòu)體來(lái)表示椅亚,對(duì)象的方法實(shí)現(xiàn)通過(guò) C 函數(shù)來(lái)實(shí)現(xiàn)限番。
- Objective-C 的面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機(jī)制動(dòng)態(tài)性決定了其不僅需要一個(gè)編譯器(疑問(wèn)思考其編譯的過(guò)程),而且還需要運(yùn)行時(shí)系統(tǒng)來(lái)動(dòng)態(tài)創(chuàng)建類(lèi)和對(duì)象呀舔,從而進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)弥虐。
- 下面上具體代碼來(lái)感受下上面的幾句話(huà)的含義,在一個(gè)控制器VC中對(duì)自定義繼承自
NSObject
的CCSomeClass
類(lèi)進(jìn)行實(shí)例化媚赖,然后調(diào)用其- (void)sendMessage:(NSString *)str;
方法:
- (void)viewDidLoad {
[super viewDidLoad];
CCSomeClass *someClass = [[CCSomeClass alloc] init];
[someClass sendMessage:@"zerocc"];
}
下面的代碼實(shí)現(xiàn)等同于上面的實(shí)現(xiàn)效果
- (void)viewDidLoad {
[super viewDidLoad];
CCSomeClass *someClass = objc_msgSend([CCSomeClass class], sel_registerName("alloc"));
someClass = objc_msgSend(someClass, sel_registerName("init"));
SEL sel = sel_registerName("sendMessage:");
objc_msgSend(someClass,sel,@"zerocc");
}
Objective-C 中類(lèi)和對(duì)象
類(lèi)與對(duì)象的概念
- 類(lèi)是對(duì)同一類(lèi)事物高度的抽象霜瘪,類(lèi)中定義了這一類(lèi)對(duì)象所應(yīng)具有的靜態(tài)屬性(屬性)和動(dòng)態(tài)屬性(方法)。
- 對(duì)象就是該類(lèi)的一個(gè)實(shí)例惧磺,對(duì)象的創(chuàng)建就是類(lèi)的實(shí)例化過(guò)程颖对。
- 可以理解為類(lèi)就是一種數(shù)據(jù)類(lèi)型,它的變量就是對(duì)象磨隘。
Objective-C 中的類(lèi)會(huì)是什么缤底?
- 在Objective-C 類(lèi)繼承中,所有的類(lèi)都是
NSObject
(NSProxy
類(lèi)拋開(kāi)不考慮) 的子類(lèi)番捂;也就是說(shuō)NSObject
是 Objective-C 繼承中的根類(lèi),其他類(lèi)都從 NSObject 繼承一套基本的接口到 Objective-C 運(yùn)行時(shí)體系中训堆。這些類(lèi)的實(shí)例又都是從 NSObject 繼承而獲得 Objective-C 最根本的特性 - NSObject特性詳解。 -
NSObject
.h 中類(lèi)的定義(interface)文件白嘁,我們可以看到如下代碼:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
每個(gè)NSObject
類(lèi)都擁有一個(gè)Class
類(lèi)作為成員變量, 并且這個(gè)根類(lèi)只有這么一個(gè) isa
屬性坑鱼;
-
Class isa
是什么呢?進(jìn)入到objc.h
文件中絮缅,看到如下:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
1. Class
是objc_class
類(lèi)型的結(jié)構(gòu)體的指針變量
? 2. 關(guān)鍵字typedef
作用是給指向結(jié)構(gòu)體的objc_class
類(lèi)型的指針起別名
? 3. Class isa
等價(jià)于 struct objc_class *isa
,所以從這里得出 isa
是一個(gè)指針鲁沥,是一個(gè)指向objc_class
結(jié)構(gòu)體的指針變量。
-
objc_class
類(lèi)型結(jié)構(gòu)體具體是? 進(jìn)入到runtime.h
文件中耕魄,看其定義:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類(lèi) 是指向父類(lèi)的
const char *name OBJC2_UNAVAILABLE; // 類(lèi)名
long version OBJC2_UNAVAILABLE; // 類(lèi)的版本信息画恰,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類(lèi)信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類(lèi)的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類(lèi)的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
objc_class
結(jié)構(gòu)體描述了一個(gè)類(lèi)的所有信息:父類(lèi)吸奴、名稱(chēng)允扇、信息缠局、變量大小、變量列表考润、方法列表狭园、協(xié)議列表等等;特別注意objc_class
結(jié)構(gòu)體中第一個(gè)數(shù)據(jù)又是指向另一個(gè)Class
的isa
指針(metaclass后面分析)糊治,所有的對(duì)象在內(nèi)存里面都有一個(gè)isa唱矛。概況的來(lái)說(shuō)這個(gè)isa
指針對(duì)應(yīng)的內(nèi)存地址中存儲(chǔ)了這些信息(類(lèi)的信息)。
objc_ivar_list
成員變量列表結(jié)構(gòu):
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;
objc_method_list
方法列表結(jié)構(gòu):
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;
}
**methodLists
指針的指針,可以動(dòng)態(tài)修改*methodLists
的值來(lái)添加成員方法,同樣解釋了Category不能添加屬性的原因,二級(jí)指針
objc_method
方法列表中的鏈表,它存儲(chǔ)了方法名藤违,方法類(lèi)型和方法實(shí)現(xiàn)
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
1. 方法名類(lèi)型為SEL,前面提到過(guò)相同名字的方法即使在不同類(lèi)中定義窃肠,它們的方法選擇器也相同。
? 2. 方法類(lèi)型method_types是個(gè)char指針刷允,其實(shí)存儲(chǔ)著方法的參數(shù)類(lèi)型和返回值類(lèi)型铭拧。
? 3. method_imp指向了方法的實(shí)現(xiàn),本質(zhì)上是一個(gè)函數(shù)指針恃锉,后面會(huì)詳細(xì)講到
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
它就是一個(gè)函數(shù)指針,這是由編譯器生成的呕臂。當(dāng)你發(fā)起一個(gè) ObjC 消息之后破托,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的歧蒋。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)土砂。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口,我們就可以繞開(kāi)消息傳遞階段谜洽,直接執(zhí)行方法萝映,這在后面會(huì)提到。
??你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類(lèi)型相同阐虚,參數(shù)都包含id和SEL類(lèi)型序臂。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類(lèi)型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的实束,通過(guò)一組id和SEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址奥秆;反之亦然。
??每個(gè)類(lèi)都有一個(gè)方法列表咸灿,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系构订。IMP有點(diǎn)類(lèi)似函數(shù)指針,指向具體的Method實(shí)現(xiàn)避矢,SEL與IMP之間的關(guān)系圖:
??獲取方法地址IMP避開(kāi)消息綁定而直接獲取方法的地址并調(diào)用方法悼瘾。這種做法很少用囊榜,除非是需要持續(xù)大量重復(fù)調(diào)用某方法的極端情況,避開(kāi)消息發(fā)送泛濫而直接調(diào)用該方法會(huì)更高效亥宿。NSObject類(lèi)中有個(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);
objc_cache
方法緩存
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指向的類(lèi)的方法列表中遍歷查找能夠響應(yīng)消息的方法箩绍,因?yàn)檫@樣效率太低了孔庭,而是優(yōu)先在Cache中查找。Runtime系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用材蛛,那么它有可能今后還會(huì)被調(diào)用)method_name作為key圆到,method_imp作為value給存起來(lái),下次查找的時(shí)候效率更高卑吭。這根計(jì)算機(jī)組成原理中學(xué)過(guò)的CPU繞過(guò)主存先訪問(wèn)Cache的道理挺像芽淡,高速緩存(cache) ->內(nèi)存->虛擬內(nèi)存->磁盤(pán)。
Objective-C方法動(dòng)態(tài)調(diào)用中涉及的術(shù)語(yǔ)介紹
OC只是在編譯階段確定了要向接收者發(fā)送message這條消息豆赏,而receive將要如何響應(yīng)這條消息挣菲,那就要看運(yùn)行時(shí)發(fā)生的情況來(lái)決定了。通過(guò)發(fā)送消息來(lái)達(dá)到動(dòng)態(tài)調(diào)用:
[obj doSomething] // 編譯時(shí)runtime會(huì)將其轉(zhuǎn)化為 objc_msgSend(obj,@selector(doSomething));
也就是說(shuō) objc_msgSend
函數(shù)相當(dāng)于入口掷邦;對(duì)象調(diào)用某個(gè)方法都將被編譯器轉(zhuǎn)化為:
id objc_msgSend(id self, SEL op, ... ); // objc_msgSend(obj, 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)容,要么就崩潰掉宣蔚。
下面將會(huì)逐漸展開(kāi)介紹一些術(shù)語(yǔ)向抢,及它們各自對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)。
id
- id 類(lèi)型參數(shù)
objc_msgSend
方法的第一個(gè)參數(shù)胚委,objc.h
文件中查看:
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
- 由此可知
id
是指向objc_object
結(jié)構(gòu)體的指針變量 -
objc_object
結(jié)構(gòu)體代表一個(gè)類(lèi)實(shí)例其包含了一個(gè)指向objc_class
類(lèi)型結(jié)構(gòu)體的指針(上面已經(jīng)分析過(guò)Class isa
)挟鸠; - 總結(jié)
id
為指向類(lèi)實(shí)例的指針,也就是他可以指向任何對(duì)象
SEL
- SEL 類(lèi)型參數(shù)
objc_msgSend
方法的第一個(gè)參數(shù)亩冬,objc.h
文件中查看:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
-
SEL
是selector
在Objc中的表示類(lèi)型艘希,selector是方法選擇器 -
SEL
可以理解為方法編號(hào),其實(shí)它就是個(gè)映射到方法的C字符串,objc.h
文件中:
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
- 可以用 Objc 編譯器命令
@selector()
或者 Runtime 系統(tǒng)的sel_registerName
函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器硅急。 - 不同類(lèi)中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的枢冤,即使方法名字相同而變量類(lèi)型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類(lèi)型(NSNumber一堆抽象工廠方法)铜秆,Cocoa 中有好多長(zhǎng)長(zhǎng)的方法....淹真。
...是方法中的實(shí)參
objc_msgSend(receiver, selector, arg1, arg2,...)
// 例如 調(diào)someClass對(duì)象發(fā)送- (void)sendMessage:(NSString *)str;方法
objc_msgSend(someClass, @selector("sendMessage:"),@"zerocc"); //
Objective-C 消息發(fā)送流程
調(diào)用方法的方式有兩種:
-
[object message]
的方式調(diào)用方法,如果一個(gè)對(duì)象無(wú)法按上述正常流程接受某一消息時(shí)连茧,就會(huì)啟動(dòng)所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制核蘸,通過(guò)這一機(jī)制巍糯,我們可以告訴對(duì)象如何處理未知的消息。默認(rèn)情況下客扎,對(duì)象接收到未知的消息祟峦,會(huì)導(dǎo)致程序崩潰,通過(guò)控制臺(tái)徙鱼,我們可以看到以下異常信息:這段異常信息實(shí)際上是由NSObject的doesNotRecognizeSelector
方法拋出的宅楞。不過(guò),我們可以采取一些措施袱吆,讓我們的程序執(zhí)行特定的邏輯厌衙,而避免程序的崩潰。 - 以 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)];
}
但是最終運(yùn)行時(shí)都是調(diào)用了objc_msgSend(receiver, selector, arg1, arg2,...)
函數(shù)筒饰,這個(gè)函數(shù)完成了動(dòng)態(tài)綁定的所有事情:
- 檢測(cè)這個(gè)selector是不是要忽略。比如Mac OS X開(kāi)發(fā)壁晒,有了垃圾回收就不理會(huì)retain, release這些函數(shù)了瓷们。
- 檢測(cè)這個(gè)target是不是nil對(duì)象。ObjC的特性是允許對(duì)一個(gè)nil對(duì)象執(zhí)行任何一個(gè)方法不會(huì)Crash讨衣,因?yàn)闀?huì)被忽略掉。
- 上面檢測(cè)都通過(guò)則開(kāi)始查找這個(gè)類(lèi)的IMP,先從cache里面找,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行式镐。
- 如果cache找不到反镇,通過(guò)對(duì)象的isa指針獲取到類(lèi)的結(jié)構(gòu)體,然后在方法分發(fā)表里面查找方法的selector (方法分發(fā)表既是:class中的方法列表method_list娘汞,它將方法選擇器和方法實(shí)現(xiàn)聯(lián)系起來(lái))歹茶。
- 如果分發(fā)表找不到,objc_msgSend 結(jié)構(gòu)體中指向父類(lèi)的指針找到其父類(lèi)你弦,并在父類(lèi)的分發(fā)表去找方法的selector惊豺,會(huì)一直沿著類(lèi)的繼承體系到達(dá)NSObject類(lèi)。一旦定位到selector禽作,函數(shù)會(huì)就獲取到了實(shí)現(xiàn)的入口點(diǎn)尸昧,并傳入相應(yīng)的參數(shù)來(lái)執(zhí)行方法的具體實(shí)現(xiàn),并將該方法添加進(jìn)入緩存中。如果最后也沒(méi)有定位到selector旷偿,則會(huì)走消息轉(zhuǎn)發(fā)流程烹俗。
在異常拋出前爆侣,Objective-C 的運(yùn)行時(shí)會(huì)給你三次拯救程序的機(jī)會(huì):
- 動(dòng)態(tài)方法解析
- 重定向接收者
- 完整的消息轉(zhuǎn)發(fā)
動(dòng)態(tài)方法解析
對(duì)象在接收到未知的消息時(shí),首先會(huì)調(diào)用所屬類(lèi)的類(lèi)方法 +resolveInstanceMethod:(實(shí)例方法)或者 +resolveClassMethod:(類(lèi)方法)幢妄。在這個(gè)方法中兔仰,我們有機(jī)會(huì)為該未知消息新增一個(gè)“處理方法”(或者說(shuō)函數(shù)實(shí)現(xiàn)),通過(guò)運(yùn)行時(shí)class_addMethod函數(shù)動(dòng)態(tài)添加到類(lèi)里面就可以了蕉鸳。
-
+ (BOOL)resolveInstanceMethod:(SEL)sel;
解析實(shí)例方法 -
+ (BOOL)resolveClassMethod:(SEL)sel;
解析類(lèi)方法 - 通過(guò)
class_addMethod(...)
的方式將缺少的selector動(dòng)態(tài)創(chuàng)建出來(lái)乎赴,前提是有提前實(shí)現(xiàn)好的IMP(method_types一致) - 如果resolveInstanceMethod:方法返回NO,運(yùn)行時(shí)就會(huì)進(jìn)行下一步:消息轉(zhuǎn)發(fā)(Message Forwarding)潮尝。
- 用處:這種方案更多的是為@dynamic屬性準(zhǔn)備的榕吼,Core Data有效用到這個(gè)方法,NSManagedObjects中的屬性的getter和setter就是在運(yùn)行時(shí)動(dòng)態(tài)添加的衍锚。
具體代碼示例:
// CCSomeClass.h
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import <Foundation/Foundation.h>
@interface CCSomeClass : NSObject
- (void)resolveMethod;
@end
// CCSomeClass.m
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import "CCSomeClass.h"
#import <objc/runtime.h>
@implementation CCSomeClass
void addResolveMethod(id obj, SEL _cmd) {
NSLog(@"resolveMethod was called ");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(resolveMethod)){
class_addMethod([self class], sel, (IMP)addResolveMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
重定向接收者
如果上一步動(dòng)態(tài)方法解析沒(méi)有處理友题,runtime會(huì)調(diào)用以下方法
-(id)forwardingTargetForSelector:(SEL)aSelector;
- 如果該方法返回非nil的對(duì)象,則使用該對(duì)象作為新的消息接收者
不能返回self戴质,會(huì)出現(xiàn)無(wú)限循環(huán) - 如果不知道該返回什么度宦,應(yīng)該使用
[super forwardingTargetForSelector:aSelector];
- 這種方法屬于單純的轉(zhuǎn)發(fā),無(wú)法對(duì)消息的參數(shù)和返回值進(jìn)行處理
- 這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個(gè)能處理該消息的對(duì)象上告匠。但這一步無(wú)法對(duì)消息進(jìn)行處理戈抄,如操作消息的參數(shù)和返回值。
具體代碼示例:
// CCSomeClass.h
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import <Foundation/Foundation.h>
@interface CCSomeClass : NSObject
- (void)forwardMethod_arrayWithString:(NSString *)str;
@end
// CCSomeClass.m
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import "CCSomeClass.h"
#import <objc/runtime.h>
#import "CCOtherClass.h"
@implementation CCSomeClass
#pragma mark - 重定向接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
{
//獲取方法名
NSString *selectorString = NSStringFromSelector(aSelector);
//根據(jù)方法名添加方法
if ([selectorString isEqualToString:@"forwardMethod_arrayWithString:"]) {
CCOtherClass *otherClass = [[CCOtherClass alloc] init];
return otherClass;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
此例中備用接受者為自定義的CCOtherclass
后专,其具體代碼:
// CCOtherClass.h
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import <Foundation/Foundation.h>
@interface CCOtherClass : NSObject
@end
// CCOtherClass.m
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import "CCOtherClass.h"
@implementation CCOtherClass
/**
* 把字符串轉(zhuǎn)換為數(shù)組
*
* @param str 需轉(zhuǎn)換的字符串
*
* @return 轉(zhuǎn)換好的數(shù)組
*/
- (NSArray *)forwardMethod_arrayWithString:(NSString *)str
{
if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) {
NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:1];
for (NSInteger index = 0; index < str.length; index++) {
[mArr addObject:[str substringWithRange:NSMakeRange(index, 1)]];
}
NSLog(@"array:::%@",mArr);
return mArr;
}
return nil;
}
@end
完整的消息轉(zhuǎn)發(fā)
- 消息轉(zhuǎn)發(fā)的第一步重寫(xiě)方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
為未有實(shí)現(xiàn)的方法的selector
提供合適的方法簽名然后返回這個(gè)aSelector的方法簽名划鸽。 - 當(dāng)對(duì)象發(fā)送一個(gè)unrecognized 的消息時(shí),會(huì)使用從上面方法獲取的方法簽名等方法信息創(chuàng)建一個(gè)表示消息的
NSInvocation
對(duì)象戚哎,把與尚未處理的消息有關(guān)的全部細(xì)節(jié)都封裝在anInvocation
中裸诽,包括selector
,目標(biāo)(target
)和參數(shù)型凳。 - 然后調(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation;
方法丈冬,在方法中又通過(guò)- (void)invokeWithTarget:(id)target;
方法選擇將消息轉(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ā)送者。
具體代碼樣例分析:
// CCSomeClass.h
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import <Foundation/Foundation.h>
@interface CCSomeClass : NSObject
- (void)usualMethod;
- (void)resolveMethod;
- (void)forwardMethod_arrayWithString:(NSString *)str;
- (void)signatureMethod_inverseWithString:(NSString *)str;
@end
#import "CCSomeClass.h"
#import <objc/runtime.h>
#import "CCOtherClass.h"
@implementation CCSomeClass
#pragma mark - 完整消息轉(zhuǎn)發(fā)
//必須重寫(xiě)這個(gè)方法槐脏,為給定的selector提供一個(gè)合適的方法簽名喉童。
// 返回aSelector的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([CCOtherClass instancesRespondToSelector:aSelector]) {
//獲取方法簽名
signature = [CCOtherClass instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
// 當(dāng)對(duì)self發(fā)送一個(gè)unrecoginzed的消息時(shí),會(huì)創(chuàng)建一個(gè)NSInvocation顿天,并調(diào)用這個(gè)方法泄朴。允許在這個(gè)方法中重抖,通過(guò)[anInvocation invokeWithTarget:otherSelf];的方式進(jìn)行消息轉(zhuǎn)發(fā)。
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//anInvocation選擇將消息轉(zhuǎn)發(fā)給其它對(duì)象
if ([CCOtherClass instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[[CCOtherClass alloc] init]];
}
}
@end
// CCOtherClass.h
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface CCOtherClass : NSObject
@end
// CCOtherClass.m
// CCRuntime
//
// Created by zerocc on 2017/4/25.
// Copyright ? 2017年 zerocc. All rights reserved.
#import "CCOtherClass.h"
@implementation CCOtherClass
/**
* 逆置字符串
*
* @param str 需逆置的字符串
*
* @return 置換后的字符串
*/
- (NSString *)signatureMethod_inverseWithString:(NSString *)str
{
if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) {
NSMutableString *mStr = [NSMutableString stringWithCapacity:1];
for (NSInteger index = str.length; index > 0; index--) {
[mStr appendString:[str substringWithRange:NSMakeRange(index - 1, 1)]];
}
NSLog(@"retureStr:::%@",mStr);
return mStr;
}
return nil;
}
@end
運(yùn)行時(shí)系統(tǒng)涉及的類(lèi)與對(duì)象操作函數(shù)
- 屬性相關(guān)操作函數(shù)
CCMyClass *myClass = [[CCMyClass alloc] init];
unsigned int outCount = 0;
Class cls = [myClass class];
// 屬性操作
objc_property_t * properties = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSLog(@"property's name: %s", propertyName);
}
free(properties);
//成員變量操作函數(shù)
// 獲取類(lèi)中指定名稱(chēng)實(shí)例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類(lèi)成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); //這個(gè)只能夠向在runtime時(shí)創(chuàng)建的類(lèi)添加成員變量
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); //必須使用free()來(lái)釋放這個(gè)數(shù)組
//屬性操作函數(shù)
// 獲取類(lèi)中指定名稱(chēng)實(shí)例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
// 獲取類(lèi)成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
- 方法相關(guān)操作函數(shù)
// 獲取一個(gè)類(lèi)方法列表
Method *methods = class_copyMethodList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methods[i];
SEL method_name = method_getName(method);
NSLog(@"method's signature - method_name: %s", method_name);
}
free(methods);
// 添加方法
//和成員變量不同的是可以為類(lèi)動(dòng)態(tài)添加方法祖灰。如果有同名會(huì)返回NO钟沛,修改的話(huà)需要使用method_setImplementation
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types) ;
// 替換原方法實(shí)現(xiàn)(偷梁換柱)
IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types) ;
// 交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2);
// 返回方法的具體實(shí)現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類(lèi)實(shí)例是否響應(yīng)指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
- 協(xié)議相關(guān)操作函數(shù)
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類(lèi)是否實(shí)現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類(lèi)實(shí)現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
Runtime黑魔法的具體運(yùn)用
- 能獲得某個(gè)類(lèi)的所有成員變量
- 能獲得某個(gè)類(lèi)的所有屬性
- 能獲得某個(gè)類(lèi)的所有方法
- 交換方法實(shí)現(xiàn)
- 能動(dòng)態(tài)添加一個(gè)成員變量
- 能動(dòng)態(tài)添加一個(gè)屬性
- 字典轉(zhuǎn)模型
- runtime歸檔/反歸檔
交換方法
- 開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能局扶,并且保持原有的功能恨统。
- 方式一:繼承系統(tǒng)的類(lèi),重寫(xiě)方法
- 方式二:使用runtime,交換方法.
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:給imageNamed方法提供功能三妈,每次加載圖片就判斷下圖片是否加載成功畜埋。
// 步驟一:先搞個(gè)分類(lèi),定義一個(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)
// 加載分類(lèi)到內(nèi)存的時(shí)候調(diào)用
+ (void)load
{
// 交換方法
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址模燥,相當(dāng)于交換實(shí)現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類(lèi)中重寫(xiě)系統(tǒng)方法imageNamed咖祭,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉,而且分類(lèi)中不能調(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
動(dòng)態(tài)添加方法
- 開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類(lèi)方法非常多么翰,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表辽旋,可以使用動(dòng)態(tài)給某個(gè)類(lèi)浩嫌,添加方法解決。
- 經(jīng)典面試題:有沒(méi)有使用performSelector补胚,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法码耐。
代碼示例:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默認(rèn)person,沒(méi)有實(shí)現(xiàn)eat方法溶其,可以通過(guò)performSelector調(diào)用骚腥,但是會(huì)報(bào)錯(cuò)。
// 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// void(*)()
// 默認(rèn)方法都有兩個(gè)隱式參數(shù)握联,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法桦沉,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過(guò)來(lái).
// 剛好可以用來(lái)判斷每瞒,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 動(dòng)態(tài)添加eat方法
// 第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
// 第二個(gè)參數(shù):添加方法的方法編號(hào)
// 第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
// 第四個(gè)參數(shù):函數(shù)的類(lèi)型金闽,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
給分類(lèi)添加屬性
- 原理:給一個(gè)類(lèi)聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類(lèi)添加關(guān)聯(lián)剿骨,并不是直接把這個(gè)值的內(nèi)存空間添加到類(lèi)存空間代芜。
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 給系統(tǒng)NSObject類(lèi)動(dòng)態(tài)添加屬性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"zerocc";
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挤庇,通過(guò)這個(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
歸檔和解檔 一鍵序列化
- 原理描述:用runtime提供的函數(shù)遍歷Model自身所有屬性钞速,并對(duì)屬性進(jìn)行encode和decode操作。
- 核心方法:在Model的基類(lèi)中重寫(xiě)方法:
如果需要實(shí)現(xiàn)一些基本數(shù)據(jù)的數(shù)據(jù)持久化(data persistance)或者數(shù)據(jù)共享(data share)嫡秕。我們可以選擇歸檔和解檔渴语。如果用一般的方法:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"nameKey"];
[aCoder encodeObject:self.gender forKey:@"genderKey"];
[aCoder encodeObject:[NSNumber numberWithInteger:self.age] forKey:@"ageKey"];
}
也可以實(shí)現(xiàn)。但是如果實(shí)體類(lèi)有很多的成員變量昆咽,這種方法很顯然就無(wú)力了驾凶。這個(gè)時(shí)候,我們就可以利用runtime來(lái)實(shí)現(xiàn)快速歸檔掷酗、解檔:
- 讓實(shí)體類(lèi)遵循<NSCoding>協(xié)議调违。并在.m文件導(dǎo)入頭文件<objc/runtime.h>。
- 實(shí)現(xiàn)
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
和- (void)encodeWithCoder:(NSCoder *)aCoder;
方法泻轰。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
//
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *propertyChar = property_getName(property);
NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
id value = [aDecoder decodeObjectForKey:propertyString];
[self setValue:value forKey:propertyString];
}
free(properties);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *propertyChar = property_getName(property);
NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
id object = [self valueForKey:propertyString];
[aCoder encodeObject:object forKey:propertyString];
}
free(properties);
}
或者這種寫(xiě)法:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
控制器的萬(wàn)能跳轉(zhuǎn)
應(yīng)用場(chǎng)景:
- 推送:根據(jù)服務(wù)端推送過(guò)來(lái)的數(shù)據(jù)規(guī)則技肩,跳轉(zhuǎn)到對(duì)應(yīng)的控制器
- 列表:不同類(lèi)似的名字,可能跳轉(zhuǎn)不同的控制器浮声,任意跳轉(zhuǎn)
- (void)testRuntime
{
NSDictionary *userInfo = @{@"class":@"CCRuntimePushVC",
@"property": @{
@"ID":@"81198",
@"type":@"2"
}
};
[self push:userInfo];
}
// 跳轉(zhuǎn)
- (void)push:(NSDictionary *)params
{
// 得到類(lèi)名
NSString *className = [NSString stringWithFormat:@"%@",params[@"class"]];
// 通過(guò)名稱(chēng)轉(zhuǎn)換成Class
Class getClass = NSClassFromString([NSString stringWithFormat:@"%@",className]);
// 判斷得到的這個(gè)class 是否存在
if (getClass) {
// 創(chuàng)建 class 對(duì)象
id creatClass = [[getClass alloc] init];
NSDictionary *propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([self checkIsExistPropertyWithInstance:creatClass verifyPropertyName:key]) {
// 利用 kvc 賦值
[creatClass setValue:obj forKey:key];
}
}];
[self.navigationController pushViewController:creatClass animated:YES];
}else{
NSLog(@"not this class,can not push");
}
}
// 檢查對(duì)象是否存在該屬性
- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
unsigned int outCount, i;
// 獲取對(duì)象的屬性列表
objc_property_t *properties = class_copyPropertyList([instance class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
// 屬性名轉(zhuǎn)換成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判斷該屬性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testRuntime];
}
參考鏈接
Objc 對(duì)象的今生今世
Objective-C 維基百科
精神病院Objective-C runtime系列
蘋(píng)果官方API文檔解釋
蘋(píng)果官方Objective-C運(yùn)行時(shí)編程指南文檔