Runtime
Runtime是什么
Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API肢预,其為 iOS 內(nèi)部的核心之一憔维,我們平時(shí)編寫的 OC 代碼,底層都是基于它來(lái)實(shí)現(xiàn)的毁枯。比如:
[receiver message];
// 底層運(yùn)行時(shí)會(huì)被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector)
// 如果其還有參數(shù)比如:
[receiver message:(id)arg...];
// 底層運(yùn)行時(shí)會(huì)被編譯器轉(zhuǎn)化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
為什么需要Runtime
Objective-C 是一門動(dòng)態(tài)語(yǔ)言,它會(huì)將一些工作放在代碼運(yùn)行時(shí)才處理而并非編譯時(shí)蚯根。也就是說(shuō)后众,有很多類和成員變量在我們編譯的時(shí)是不知道的胀糜,而在運(yùn)行時(shí)颅拦,我們所編寫的代碼會(huì)轉(zhuǎn)換成完整的確定的代碼運(yùn)行。
因此教藻,編譯器是不夠的距帅,我們還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(Runtime system)來(lái)處理編譯后的代碼。
Runtime 基本是用 C 和匯編寫的括堤,由此可見(jiàn)蘋果為了動(dòng)態(tài)系統(tǒng)的高效而做出的努力碌秸。蘋果和 GNU 各自維護(hù)一個(gè)開源的 Runtime 版本,這兩個(gè)版本之間都在努力保持一致悄窃。
Runtime 的作用
OC 在三種層面上與 Runtime 系統(tǒng)進(jìn)行交互:
1.通過(guò) Objective-C 源代碼
只需要編寫 OC 代碼讥电,Runtime 系統(tǒng)自動(dòng)在幕后搞定一切,調(diào)用方法轧抗,編譯器會(huì)將 OC 代碼轉(zhuǎn)換成運(yùn)行時(shí)代碼恩敌,在運(yùn)行時(shí)確定數(shù)據(jù)結(jié)構(gòu)和函數(shù)。
2.通過(guò) Foundation 框架的 NSObject 類定義的方法
Cocoa 程序中絕大部分類都是 NSObject 類的子類横媚,所以都繼承了 NSObject 的行為纠炮。(NSProxy 類時(shí)個(gè)例外月趟,它是個(gè)抽象超類)
一些情況下,NSObject 類僅僅定義了完成某件事情的模板恢口,并沒(méi)有提供所需要的代碼孝宗。例如 - description方法,該方法返回類內(nèi)容的字符串表示耕肩,該方法主要用來(lái)調(diào)試程序因妇。NSObject類并不知道子類的內(nèi)容,所以它只是返回類的名字和對(duì)象的地址看疗,NSObject的子類可以重新實(shí)現(xiàn)沙峻。
還有一些NSObject的方法可以從Runtime系統(tǒng)中獲取信息,允許對(duì)象進(jìn)行自我檢查两芳。例如:
-class方法返回對(duì)象的類摔寨;
-isKindOfClass: 和 -isMemberOfClass: 方法檢查對(duì)象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者當(dāng)前類的成員變量);
-respondsToSelector: 檢查對(duì)象能否響應(yīng)指定的消息怖辆;
-conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法是复;
-methodForSelector: 返回指定方法實(shí)現(xiàn)的地址。
3.通過(guò)對(duì) Runtime 庫(kù)函數(shù)的直接調(diào)用
Runtime 系統(tǒng)是具有公共接口的動(dòng)態(tài)共享庫(kù)竖螃。頭文件存放于/usr/include/objc目錄下淑廊,這意味著我們使用時(shí)只需要引入objc/Runtime.h頭文件即可。
許多函數(shù)可以讓你使用純 C 代碼來(lái)實(shí)現(xiàn) Objc 中同樣的功能特咆。除非是寫一些 Objc 與其他語(yǔ)言的橋接或是底層的 debug 工作季惩,你在寫 Objc 代碼時(shí)一般不會(huì)用到這些 C 語(yǔ)言函數(shù)。
Runtime的相關(guān)術(shù)語(yǔ)
1.SEL
它是selector在 Objc 中的表示(Swift 中是 Selector 類)腻格。selector 是方法選擇器画拾,其實(shí)作用就和名字一樣,日常生活中菜职,我們通過(guò)人名辨別誰(shuí)是誰(shuí)青抛,注意 Objc 在相同的類中不會(huì)有命名相同的兩個(gè)方法。selector 對(duì)方法名進(jìn)行包裝酬核,以便找到對(duì)應(yīng)的方法實(shí)現(xiàn)蜜另。它的數(shù)據(jù)結(jié)構(gòu)是:
typedef struct objc_selector *SEL;
我們可以看出它是個(gè)映射到方法的 C 字符串,你可以通過(guò) Objc 編譯器器命令@selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來(lái)獲取一個(gè) SEL 類型的方法選擇器嫡意。
注意:不同類中相同名字的方法所對(duì)應(yīng)的 selector 是相同的举瑰,由于變量的類型不同,所以不會(huì)導(dǎo)致它們調(diào)用方法實(shí)現(xiàn)混亂蔬螟。
2.id
id 是一個(gè)參數(shù)類型此迅,它是指向某個(gè)類的實(shí)例的指針。定義如下:
typedef struct objc_object *id;
struct objc_object { Class isa; };
以上定義,看到 objc_object 結(jié)構(gòu)體包含一個(gè) isa 指針邮屁,根據(jù) isa 指針就可以找到對(duì)象所屬的類整袁。
注意:isa 指針在代碼運(yùn)行時(shí)并不總指向?qū)嵗龑?duì)象所屬的類型,所以不能依靠它來(lái)確定類型佑吝,要想確定類型還是需要用對(duì)象的 -class 方法坐昙。PS:KVO 的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的 isa 指針指向一個(gè)中間類而不是真實(shí)類型。
3.Class
typedef struct objc_class *Class;
Class 其實(shí)是指向 objc_class 結(jié)構(gòu)體的指針芋忿。objc_class 的數(shù)據(jù)結(jié)構(gòu)如下:
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;
從 objc_class 可以看到炸客,一個(gè)運(yùn)行時(shí)類中關(guān)聯(lián)了它的父類指針、類名戈钢、成員變量痹仙、方法、緩存以及附屬的協(xié)議殉了。
其中 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;
}
由此可見(jiàn)开仰,我們可以動(dòng)態(tài)修改 *methodList 的值來(lái)添加成員方法,這也是 Category 實(shí)現(xiàn)的原理薪铜,同樣解釋了 Category 不能添加屬性的原因众弓。
objc_ivar_list 結(jié)構(gòu)體用來(lái)存儲(chǔ)成員變量的列表,而 objc_ivar 則是存儲(chǔ)了單個(gè)成員變量的信息隔箍;同理谓娃,objc_method_list 結(jié)構(gòu)體存儲(chǔ)著方法數(shù)組的列表,而單個(gè)方法的信息則由 objc_method 結(jié)構(gòu)體存儲(chǔ)蜒滩。
值得注意的時(shí)滨达,objc_class 中也有一個(gè) isa 指針,這說(shuō)明 Objc 類本身也是一個(gè)對(duì)象俯艰。為了處理類和對(duì)象的關(guān)系捡遍,Runtime 庫(kù)創(chuàng)建了一種叫做 Meta Class(元類) 的東西,類對(duì)象所屬的類就叫做元類蟆炊。Meta Class 表述了類對(duì)象本身所具備的元數(shù)據(jù)稽莉。
我們所熟悉的類方法瀑志,就源自于 Meta Class涩搓。我們可以理解為類方法就是類對(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í)例。所有元類的 isa 指針最終都指向根元類浇冰。
所以當(dāng) [NSObject alloc] 這條消息發(fā)送給類對(duì)象的時(shí)候贬媒,運(yùn)行時(shí)代碼 objc_msgSend() 會(huì)去它元類中查找能夠響應(yīng)消息的方法實(shí)現(xiàn),如果找到了肘习,就會(huì)對(duì)這個(gè)類對(duì)象執(zhí)行方法調(diào)用际乘。
最后 objc_class 中還有一個(gè) objc_cache ,緩存漂佩,它的作用很重要脖含,后面會(huì)提到。
4.Method
Method 代表類中某個(gè)方法的類型
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method 存儲(chǔ)了方法名投蝉,方法類型和方法實(shí)現(xiàn):
方法名類型為 SEL
方法類型 method_types 是個(gè) char 指針养葵,存儲(chǔ)方法的參數(shù)類型和返回值類型
method_imp 指向了方法的實(shí)現(xiàn),本質(zhì)是一個(gè)函數(shù)指針
Ivar
Ivar 是表示成員變量的類型瘩缆。
typedef struct objc_ivar *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
}
其中 ivar_offset 是基地址偏移字節(jié)
5.IMP
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個(gè)函數(shù)指針关拒,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后庸娱,最終它會(huì)執(zhí)行的那段代碼夏醉,就是由這個(gè)函數(shù)指針指定的。而 IMP 這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)涌韩。
如果得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口畔柔,我們就可以繞開消息傳遞階段,直接執(zhí)行方法臣樱,這在后面 Cache 中會(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)地址棚放。
而一個(gè)確定的方法也只有唯一的一組 id 和 SEL 參數(shù)枚粘。
6.Cache
Cache 定義如下:
typedef struct objc_cache *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 指針指向的類的方法列表中遍歷查找能夠響應(yīng)的方法馍迄,因?yàn)槊看味家檎倚侍土耍莾?yōu)先在 Cache 中查找局骤。
Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 Cache 中攀圈,如果一個(gè)方法被調(diào)用,那么它有可能今后還會(huì)被調(diào)用峦甩,下次查找的時(shí)候就會(huì)效率更高赘来。就像計(jì)算機(jī)組成原理中 CPU 繞過(guò)主存先訪問(wèn) Cache 一樣。
7.Property
typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個(gè)更常用
可以通過(guò)class_copyPropertyList 和 protocol_copyPropertyList 方法獲取類和協(xié)議中的屬性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
注意:
返回的是屬性列表,列表中每個(gè)元素都是一個(gè) objc_property_t 指針
#import <Foundation/Foundation.h>
@interface Person : NSObject
/** 姓名 */
@property (strong, nonatomic) NSString *name;
/** age */
@property (assign, nonatomic) int age;
/** weight */
@property (assign, nonatomic) double weight;
@end
以上是一個(gè) Person 類犬辰,有3個(gè)屬性嗦篱。讓我們用上述方法獲取類的運(yùn)行時(shí)屬性。
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
NSLog(@"%d", outCount);
for (NSInteger i = 0; i < outCount; i++) {
NSString *name = @(property_getName(properties[i]));
NSString *attributes = @(property_getAttributes(properties[i]));
NSLog(@"%@--------%@", name, attributes);
}
打印結(jié)果如下:
test[2321:451525] 3
test[2321:451525] name--------T@"NSString",&,N,V_name
test[2321:451525] age--------Ti,N,V_age
test[2321:451525] weight--------Td,N,V_weight
property_getName 用來(lái)查找屬性的名稱幌缝,返回 c 字符串默色。property_getAttributes 函數(shù)挖掘?qū)傩缘恼鎸?shí)名稱和 @encode 類型,返回 c 字符串狮腿。
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
class_getProperty 和 protocol_getProperty 通過(guò)給出屬性名在類和協(xié)議中獲得屬性的引用腿宰。
Runtime與消息
一些 Runtime 術(shù)語(yǔ)講完了,接下來(lái)就要說(shuō)到消息了缘厢。體會(huì)蘋果官方文檔中的 messages aren’t bound to method implementations until Runtime吃度。消息直到運(yùn)行時(shí)才會(huì)與方法實(shí)現(xiàn)進(jìn)行綁定。
-
這里要清楚一點(diǎn)贴硫,objc_msgSend 方法看清來(lái)好像返回了數(shù)據(jù)椿每,其實(shí)objc_msgSend 從不返回?cái)?shù)據(jù),而是你的方法在運(yùn)行時(shí)實(shí)現(xiàn)被調(diào)用后才會(huì)返回?cái)?shù)據(jù)英遭。下面詳細(xì)敘述消息發(fā)送的步驟:
- 首先檢測(cè)這個(gè) selector 是不是要忽略间护。比如 Mac OS X 開發(fā),有了垃圾回收就不理會(huì) retain挖诸,release 這些函數(shù)汁尺。
- 檢測(cè)這個(gè) selector 的 target 是不是 nil,Objc 允許我們對(duì)一個(gè) nil 對(duì)象執(zhí)行任何方法不會(huì) Crash多律,因?yàn)檫\(yùn)行時(shí)會(huì)被忽略掉痴突。
- 如果上面兩步都通過(guò)了,那么就開始查找這個(gè)類的實(shí)現(xiàn) IMP狼荞,先從 cache 里查找甜无,如果找到了就運(yùn)行對(duì)應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼霸株。
- 如果 cache 找不到就找類的方法列表中是否有對(duì)應(yīng)的方法浑彰。
- 如果類的方法列表中找不到就到父類的方法列表中查找爷狈,一直找到 NSObject 類為止。
如果還找不到丰涉,就要開始進(jìn)入動(dòng)態(tài)方法解析了拓巧,后面會(huì)提到。
在消息的傳遞中昔搂,編譯器會(huì)根據(jù)情況在 objc_msgSend 玲销, objc_msgSend_stret 输拇, objc_msgSendSuper 摘符, objc_msgSendSuper_stret 這四個(gè)方法中選擇一個(gè)調(diào)用。如果消息是傳遞給父類,那么會(huì)調(diào)用名字帶有 Super 的函數(shù)逛裤,如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡(jiǎn)單值時(shí)瘩绒,會(huì)調(diào)用名字帶有 stret 的函數(shù)。
方法中的隱藏參數(shù)
疑問(wèn):
我們經(jīng)常用到關(guān)鍵字 self 带族,但是 self 是如何獲取當(dāng)前方法的對(duì)象呢锁荔?
其實(shí),這也是 Runtime 系統(tǒng)的作用蝙砌,self 實(shí)在方法運(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)容肚邢,當(dāng)前方法的對(duì)象指針)
方法選擇器(_cmd 指向的內(nèi)容壹堰,當(dāng)前方法的 SEL 指針)
因?yàn)樵谠创a方法的定義中,我們并沒(méi)有發(fā)現(xiàn)這兩個(gè)參數(shù)的聲明骡湖。它們時(shí)在代碼被編譯時(shí)被插入方法實(shí)現(xiàn)中的贱纠。盡管這些參數(shù)沒(méi)有被明確聲明,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?
這兩個(gè)參數(shù)中响蕴, self更實(shí)用谆焊。它是在方法實(shí)現(xiàn)中訪問(wèn)消息接收者對(duì)象的實(shí)例變量的途徑。
這時(shí)我們可能會(huì)想到另一個(gè)關(guān)鍵字 super 浦夷,實(shí)際上 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 本身军拟,當(dāng)我們想通過(guò) [super class] 獲取父類時(shí)剃执,編譯器其實(shí)是將指向 self 的 id 指針和 class 的 SEL 傳遞給了 objc_msgSendSuper 函數(shù)。只有在 NSObject 類中才能找到 class 方法懈息,然后 class 方法底層被轉(zhuǎn)換為 object_getClass()肾档, 接著底層編譯器將代碼轉(zhuǎn)換為 objc_msgSend(objc_super->receiver, @selector(class)),傳入的第一個(gè)參數(shù)是指向 self 的 id 指針辫继,與調(diào)用 [self class] 相同怒见,所以我們得到的永遠(yuǎn)都是 self 的類型。因此你會(huì)發(fā)現(xiàn):
// 這句話并不能獲取父類的類型姑宽,只能獲取當(dāng)前類的類型名
NSLog(@"%@", NSStringFromClass([super class]));
獲取方法地址
NSObject 類中有一個(gè)實(shí)例方法:methodForSelector遣耍,你可以用它來(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í)炮车,兩個(gè)隱藏參數(shù)也必須明確給出舵变,上面的例子調(diào)用了1000次函數(shù)酣溃,你也可以嘗試給 target 發(fā)送1000次 setFilled: 消息會(huì)花多久。
雖然可以更高效的調(diào)用方法纪隙,但是這種做法很少用赊豌,除非時(shí)需要持續(xù)大量重復(fù)調(diào)用某個(gè)方法的情況,才會(huì)選擇使用以免消息發(fā)送泛濫绵咱。
注意:
methodForSelector:方法是由 Runtime 系統(tǒng)提供的碘饼,而不是 Objc 自身的特性
動(dòng)態(tài)方法解析
你可以動(dòng)態(tài)提供一個(gè)方法實(shí)現(xiàn)。如果我們使用關(guān)鍵字 @dynamic 在類的實(shí)現(xiàn)文件中修飾一個(gè)屬性悲伶,表明我們會(huì)為這個(gè)屬性動(dòng)態(tài)提供存取方法艾恼,編譯器不會(huì)再默認(rèn)為我們生成這個(gè)屬性的 setter 和 getter 方法了,需要我們自己提供麸锉。
@dynamic propertyName;
這時(shí)蒂萎,我們可以通過(guò)分別重載 resolveInstanceMethod: 和 resolveClassMethod: 方法添加實(shí)例方法實(shí)現(xiàn)和類方法實(shí)現(xiàn)。
當(dāng) Runtime 系統(tǒng)在 Cache 和類的方法列表(包括父類)中找不到要執(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)表示的含義見(jiàn):Type Encoding
注意:
動(dòng)態(tài)方法解析會(huì)在消息轉(zhuǎn)發(fā)機(jī)制侵入前執(zhí)行,動(dòng)態(tài)方法解析器將會(huì)首先給予提供該方法選擇器對(duì)應(yīng)的 IMP 的機(jī)會(huì)忽媒。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制争拐,就讓 resolveInstanceMethod: 方法返回 NO。
消息轉(zhuǎn)發(fā)
1.重定向
消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前晦雨,Runtime 系統(tǒng)允許我們替換消息的接收者為其他對(duì)象架曹。通過(guò) - (id)forwardingTargetForSelector:(SEL)aSelector 方法。
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
如果此方法返回 nil 或者 self闹瞧,則會(huì)計(jì)入消息轉(zhuǎn)發(fā)機(jī)制(forwardInvocation:)绑雄,否則將向返回的對(duì)象重新發(fā)送消息。
2.轉(zhuǎn)發(fā)
當(dāng)動(dòng)態(tài)方法解析不做處理返回 NO 時(shí)奥邮,則會(huì)觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制万牺。這時(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ì)不能處理的消息做一些處理。也可以將消息轉(zhuǎn)發(fā)給其他對(duì)象處理蘸朋,而不拋出錯(cuò)誤核无。
注意:參數(shù) anInvocation 是從哪來(lái)的?
在 forwardInvocation: 消息發(fā)送前藕坯,Runtime 系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector: 消息团南,并取到返回的方法簽名用于生成 NSInvocation 對(duì)象噪沙。所以重寫 forwardInvocation: 的同時(shí)也要重寫 methodSignatureForSelector: 方法,否則會(huì)拋異常已慢。
當(dāng)一個(gè)對(duì)象由于沒(méi)有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法相應(yīng)某消息時(shí)曲聂,運(yùn)行時(shí)系統(tǒng)將通過(guò) forwardInvocation: 消息通知該對(duì)象霹购。每個(gè)對(duì)象都繼承了 forwardInvocation: 方法佑惠。但是, NSObject 中的方法實(shí)現(xiàn)只是簡(jiǎn)單的調(diào)用了 doesNotRecognizeSelector:齐疙。通過(guò)實(shí)現(xiàn)自己的 forwardInvocation: 方法膜楷,我們可以將消息轉(zhuǎn)發(fā)給其他對(duì)象。
forwardInvocation: 方法就是一個(gè)不能識(shí)別消息的分發(fā)中心贞奋,將這些不能識(shí)別的消息轉(zhuǎn)發(fā)給不同的接收對(duì)象赌厅,或者轉(zhuǎn)發(fā)給同一個(gè)對(duì)象,再或者將消息翻譯成另外的消息轿塔,亦或者簡(jiǎn)單的“吃掉”某些消息特愿,因此沒(méi)有響應(yīng)也不會(huì)報(bào)錯(cuò)。這一切都取決于方法的具體實(shí)現(xiàn)勾缭。
注意:
forwardInvocation:方法只有在消息接收對(duì)象中無(wú)法正常響應(yīng)消息時(shí)才會(huì)被調(diào)用揍障。所以,如果我們向往一個(gè)對(duì)象將一個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象時(shí)俩由,要確保這個(gè)對(duì)象不能有該消息的所對(duì)應(yīng)的方法毒嫡。否則,forwardInvocation:將不可能被調(diào)用幻梯。
轉(zhuǎn)發(fā)和多繼承
轉(zhuǎn)發(fā)和繼承相似兜畸,可用于為 Objc 編程添加一些多繼承的效果。就像下圖那樣碘梢,一個(gè)對(duì)象把消息轉(zhuǎn)發(fā)出去咬摇,就好像它把另一個(gè)對(duì)象中的方法接過(guò)來(lái)或者“繼承”過(guò)來(lái)一樣。
這使得在不同繼承體系分支下的兩個(gè)類可以實(shí)現(xiàn)“繼承”對(duì)方的方法煞躬,在上圖中 Warrior 和 Diplomat 沒(méi)有繼承關(guān)系菲嘴,但是 Warrior 將 negotiate 消息轉(zhuǎn)發(fā)給了 Diplomat 后,就好似 Diplomat 是 Warrior 的超類一樣汰翠。
消息轉(zhuǎn)發(fā)彌補(bǔ)了 Objc 不支持多繼承的性質(zhì)龄坪,也避免了因?yàn)槎嗬^承導(dǎo)致單個(gè)類變得臃腫復(fù)雜。
轉(zhuǎn)發(fā)與繼承
雖然轉(zhuǎn)發(fā)可以實(shí)現(xiàn)繼承的功能复唤,但是 NSObject 還是必須表面上很嚴(yán)謹(jǐn)健田,像 respondsToSelector: 和 isKindOfClass: 這類方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈佛纫。
Warrior 對(duì)象被問(wèn)到是否能響應(yīng) negotiate消息:
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
回答當(dāng)然是 NO妓局, 盡管它能接受 negotiate 消息而不報(bào)錯(cuò)总放,因?yàn)樗哭D(zhuǎn)發(fā)消息給 Diplomat 類響應(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è)方法標(biāo)簽來(lái)返回準(zhǔn)確的方法描述 methodSignatureForSelector:穆桂,這個(gè)方法會(huì)最終響應(yīng)被轉(zhuǎn)發(fā)的消息宫盔。從而生成一個(gè)確定的 NSInvocation 對(duì)象描述消息和消息參數(shù)。這個(gè)方法最終響應(yīng)被轉(zhuǎn)發(fā)的消息享完。它需要像下面這樣實(shí)現(xiàn):
- (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í)例變量的內(nèi)存布局就形成了彼绷,它表明訪問(wèn)類的實(shí)例變量的位置。實(shí)例變量一次根據(jù)自己所占空間而產(chǎn)生位移:
上圖左是 NSObject 類的實(shí)例變量布局茴迁。右邊是我們寫的類的布局寄悯。這樣子有一個(gè)很大的缺陷,就是缺乏拓展性笋熬。哪天蘋果更新了 NSObject 類的話热某,就會(huì)出現(xiàn)問(wèn)題:
我們自定義的類的區(qū)域和父類的區(qū)域重疊了。只有蘋果將父類改為以前的布局才能拯救我們胳螟,但這樣導(dǎo)致它們不能再拓展它們的框架了昔馋,因?yàn)槌蓡T變量布局被固定住了。在脆弱的實(shí)例變量(Fragile ivar)環(huán)境下糖耸,需要我們重新編譯繼承自 Apple 的類來(lái)恢復(fù)兼容秘遏。如果是健壯的實(shí)例變量的話,如下圖:
在健壯的實(shí)例變量下嘉竟,編譯器生成的實(shí)例變量布局跟以前一樣邦危,但是當(dāng) Runtime 系統(tǒng)檢測(cè)到與父類有部分重疊時(shí)它會(huì)調(diào)整你新添加的實(shí)例變量的位移,那樣你再子類中新添加的成員變量就被保護(hù)起來(lái)了舍扰。
注意:
在健壯的實(shí)例變量下倦蚪,不要使用 siof(SomeClass),而是用 class_getInstanceSize([SomeClass class]) 代替边苹;也不要使用 offsetof(SomeClass, SomeIvar)陵且,而要使用 ivar_getOffset(class_getInstanceVariable([SomeClass class], "SomeIvar")) 來(lái)代替。
總結(jié)
我們讓自己的類繼承自 NSObject 不僅僅是因?yàn)榛愑泻芏鄰?fù)雜的內(nèi)存分配問(wèn)題个束,更是因?yàn)檫@使得我們可以享受到 Runtime 系統(tǒng)帶來(lái)的便利慕购。
雖然平時(shí)我們很少會(huì)考慮一句簡(jiǎn)單的調(diào)用方法聊疲,發(fā)送消息底層所做的復(fù)雜的操作,但深入理解 Runtime 系統(tǒng)的細(xì)節(jié)使得我們可以利用消息機(jī)制寫出功能更強(qiáng)大的代碼沪悲。
runtime實(shí)現(xiàn)的機(jī)制是什么,怎么用获洲,一般用于干嘛. 你還能記得你所使用的相關(guān)的頭文件或者某些方法的名稱嗎?
- 需要導(dǎo)入<objc/message.h><objc/runtime.h>
- runtime殿如,運(yùn)行時(shí)機(jī)制贡珊,它是一套C語(yǔ)言庫(kù)
- 實(shí)際上我們編寫的所有OC代碼,最終都是轉(zhuǎn)成了runtime庫(kù)的東西握截,比如類轉(zhuǎn)成了runtime庫(kù)里面的結(jié)構(gòu)體等數(shù)據(jù)類型飞崖,方法轉(zhuǎn)成了runtime庫(kù)里面的C語(yǔ)言函數(shù)烂叔,平時(shí)調(diào)方法都是轉(zhuǎn)成了objc_msgSend函數(shù)(所以說(shuō)OC有個(gè)消息發(fā)送機(jī)制)
- 因此谨胞,可以說(shuō)runtime是OC的底層實(shí)現(xiàn),是OC的幕后執(zhí)行者
- 有了runtime庫(kù)蒜鸡,能做什么事情呢胯努?runtime庫(kù)里面包含了跟類、成員變量逢防、方法相關(guān)的API叶沛,比如獲取類里面的所有成員變量,為類動(dòng)態(tài)添加成員變量忘朝,動(dòng)態(tài)改變類的方法實(shí)現(xiàn)灰署,為類動(dòng)態(tài)添加新的方法等
- 因此,有了runtime局嘁,想怎么改就怎么改
Objective-C 如何對(duì)已有的方法溉箕,添加自己的功能代碼以實(shí)現(xiàn)類似記錄日志這樣的功能?
這題目主要考察的是runtime如何交換方法悦昵。先在分類中添加一個(gè)方法,注意不能重寫系統(tǒng)方法,會(huì)覆蓋
+(NSString *)myLog
{
// 這里寫打印行號(hào),什么方法,哪個(gè)類調(diào)用等等
}
// 加載分類到內(nèi)存的時(shí)候調(diào)用
+(void)load
{
// 獲取imageWithName方法地址
Method description = class_getClassMethod(self, @selector(description));
// 獲取imageWithName方法地址
Method myLog = class_getClassMethod(self, @selector(myLog));
// 交換方法地址肴茄,相當(dāng)于交換實(shí)現(xiàn)方式
method_exchangeImplementations(description, myLog);
}
如何讓 Category 支持屬性?
使用runtime可以實(shí)現(xiàn)
頭文件
@interface NSObject (test)
@property (nonatomic, copy) NSString *name;
@end
.m文件
@implementation NSObject (test)
// 定義關(guān)聯(lián)的key
static const char *key = "name";
-(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);
}
Toll-Free Bridging 是什么棋凳?什么情況下會(huì)使用拦坠?
Toll-Free Bridging用于在Foundation對(duì)象與Core Foundation對(duì)象之間交換數(shù)據(jù),俗稱橋接
在ARC環(huán)境下,Foundation對(duì)象轉(zhuǎn)成 Core Foundation對(duì)象
使用__bridge橋接以后ARC會(huì)自動(dòng)管理2個(gè)對(duì)象
使用__bridge_retained橋接需要手動(dòng)釋放Core Foundation對(duì)象
在ARC環(huán)境下, Core Foundation對(duì)象轉(zhuǎn)成 Foundation對(duì)象
使用__bridge橋接,如果Core Foundation對(duì)象被釋放,Foundation對(duì)象也同時(shí)不能使用了,需要手動(dòng)管理Core Foundation對(duì)象
使用__bridge_transfer橋接,系統(tǒng)會(huì)自動(dòng)管理2個(gè)對(duì)象
performSelector:withObject:afterDelay: 內(nèi)部大概是怎么實(shí)現(xiàn)的,有什么注意事項(xiàng)么剩岳?
- 創(chuàng)建一個(gè)定時(shí)器,時(shí)間結(jié)束后系統(tǒng)會(huì)使用runtime通過(guò)方法名稱(Selector本質(zhì)就是方法名稱)去方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn)并調(diào)用方法
- 注意事項(xiàng)
- 調(diào)用performSelector:withObject:afterDelay:方法時(shí),先判斷希望調(diào)用的方法是否存在respondsToSelector:
- 這個(gè)方法是異步方法,必須在主線程調(diào)用,在子線程調(diào)用永遠(yuǎn)不會(huì)調(diào)用到想調(diào)用的方法
什么是 Method Swizzle(黑魔法)贞滨,什么情況下會(huì)使用?
- 在沒(méi)有一個(gè)類的實(shí)現(xiàn)源碼的情況下卢肃,想改變其中一個(gè)方法的實(shí)現(xiàn)疲迂,除了繼承它重寫才顿、和借助類別重名方法暴力搶先之外,還有更加靈活的方法Method Swizzle尤蒿。
- Method swizzling指的是改變一個(gè)已存在的選擇器對(duì)應(yīng)的實(shí)現(xiàn)的過(guò)程郑气。OC中方法的調(diào)用能夠在運(yùn)行時(shí)通過(guò)改變——通過(guò)改變類的調(diào)度表(dispatch table)中選擇器到最終函數(shù)間的映射關(guān)系。
- 在OC中調(diào)用一個(gè)方法腰池,其實(shí)是向一個(gè)對(duì)象發(fā)送消息尾组,查找消息的唯一依據(jù)是selector的名字。利用OC的動(dòng)態(tài)特性示弓,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn)讳侨。
- 每個(gè)類都有一個(gè)方法列表,存放著selector的名字和方法實(shí)現(xiàn)的映射關(guān)系奏属。IMP有點(diǎn)類似函數(shù)指針跨跨,指向具體的Method實(shí)現(xiàn)。
- 我們可以利用 method_exchangeImplementations 來(lái)交換2個(gè)方法中的IMP囱皿,
- 我們可以利用 class_replaceMethod 來(lái)修改類勇婴,
- 我們可以利用 method_setImplementation 來(lái)直接設(shè)置某個(gè)方法的IMP,
- 歸根結(jié)底嘱腥,都是偷換了selector的IMP
能否向編譯后得到的類中增加實(shí)例變量耕渴?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么齿兔?
- 不能向編譯后得到的類中增加實(shí)例變量橱脸;
- 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量;
解釋如下:
因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中分苇,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定添诉,同時(shí)runtime 會(huì)調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來(lái)處理 strong weak 引用。所以不能向存在的類中添加實(shí)例變量组砚;
運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量吻商,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后糟红,objc_registerClassPair 之前艾帐,原因同上。
為什么其他語(yǔ)言里叫函數(shù)調(diào)用盆偿, objective c里則是給對(duì)象發(fā)消息(或者談下對(duì)runtime的理解)
先來(lái)看看怎么理解發(fā)送消息的含義:
[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)決定了催什。
OC 的 Runtime 鑄就了它動(dòng)態(tài)語(yǔ)言的特性,Objc Runtime使得C具有了面向?qū)ο竽芰υ姿诔绦蜻\(yùn)行時(shí)創(chuàng)建蒲凶,檢查,修改類拆内、對(duì)象和它們的方法旋圆。可以使用runtime的一系列方法實(shí)現(xiàn)麸恍。
順便附上OC中一個(gè)類的數(shù)據(jù)結(jié)構(gòu) /usr/include/objc/runtime.h
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class灵巧,因?yàn)镺bjc的類的本身也是一個(gè)Object,為了處理這個(gè)關(guān)系或南,runtime就創(chuàng)造了Meta Class孩等,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時(shí)艾君,實(shí)際上是把這個(gè)消息發(fā)給了Class Object
#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; // 方法緩存冰垄,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象蹬癌,這時(shí)會(huì)在method Lists中遍歷,如果cache了虹茶,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率逝薪。
struct objc_protocol_list *protocols
OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
OC中一個(gè)類的對(duì)象實(shí)例的數(shù)據(jù)結(jié)構(gòu)(/usr/include/objc/objc.h):
typedef struct objc_class *Class; /// 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;
向object發(fā)送消息時(shí),Runtime庫(kù)會(huì)根據(jù)object的isa指針找到這個(gè)實(shí)例object所屬于的類蝴罪,然后在類的方法列表以及父類方法列表尋找對(duì)應(yīng)的方法運(yùn)行董济。id是一個(gè)objc_object結(jié)構(gòu)類型的指針,這個(gè)類型的對(duì)象能夠轉(zhuǎn)換成任何一種對(duì)象要门。
然后再來(lái)看看消息發(fā)送的函數(shù):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ì)提到居暖。
后面還有:
動(dòng)態(tài)方法解析resolveThisMethodDynamically
消息轉(zhuǎn)發(fā)forwardingTargetForSelector
runtime如何實(shí)現(xiàn)weak屬性顽频?
? 通過(guò)關(guān)聯(lián)屬性來(lái)實(shí)現(xiàn):
? // 聲明一個(gè)weak屬性,這里假設(shè)delegate太闺,其實(shí)weak關(guān)鍵字可以不使用糯景,
? // 因?yàn)槲覀冎貙懥薵etter/setter方法
? @property (nonatomic, weak) id delegate;
?
? - (id)delegate {
? return objc_getAssociatedObject(self, @"__delegate__key");
? }
?
? // 指定使用OBJC_ASSOCIATION_ASSIGN,官方注釋是:
? // Specifies a weak reference to the associated object.
? // 也就是說(shuō)對(duì)于對(duì)象類型省骂,就是weak了
? - (void)setDelegate:(id)delegate {
? objc_setAssociatedObject(self, @"__delegate__key", delegate, OBJC_ASSOCIATION_ASSIGN);
? }
? 通過(guò)objc_storeWeak函數(shù)來(lái)實(shí)現(xiàn)蟀淮,不過(guò)這種方式幾乎沒(méi)有遇到有人這么使用過(guò),因?yàn)檫@里不細(xì)說(shuō)了钞澳。
runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址怠惶?
- 每個(gè)selector都與對(duì)應(yīng)的IMP是一一對(duì)應(yīng)的關(guān)系,通過(guò)selector就可以直接找到對(duì)應(yīng)的IMP:
objc_msgForward函數(shù)是做什么的轧粟,直接調(diào)用它將會(huì)發(fā)生什么策治?
_objc_msgForward是IMP類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息兰吟,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候通惫,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
IMP msgForward = _objc_msgForward;
如果手動(dòng)調(diào)用objcmsgForward混蔼,將跳過(guò)查找IMP的過(guò)程履腋,而是直接觸發(fā)“消息轉(zhuǎn)發(fā)”,進(jìn)入如下流程:
? 第一步:+ (BOOL)resolveInstanceMethod:(SEL)sel實(shí)現(xiàn)方法惭嚣,指定是否動(dòng)態(tài)添加方法遵湖。若返回NO,則進(jìn)入下一步料按,若返回YES奄侠,則通過(guò)class_addMethod函數(shù)動(dòng)態(tài)地添加方法,消息得到處理载矿,此流程完畢垄潮。
? 第二步:在第一步返回的是NO時(shí)烹卒,就會(huì)進(jìn)入- (id)forwardingTargetForSelector:(SEL)aSelector方法,這是運(yùn)行時(shí)給我們的第二次機(jī)會(huì)弯洗,用于指定哪個(gè)對(duì)象響應(yīng)這個(gè)selector旅急。不能指定為self。若返回nil牡整,表示沒(méi)有響應(yīng)者藐吮,則會(huì)進(jìn)入第三步。若返回某個(gè)對(duì)象逃贝,則會(huì)調(diào)用該對(duì)象的方法谣辞。
? 第三步:若第二步返回的是nil,則我們首先要通過(guò)- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法簽名沐扳,若返回nil泥从,則表示不處理。若返回方法簽名沪摄,則會(huì)進(jìn)入下一步躯嫉。
? 第四步:當(dāng)?shù)谌椒祷胤椒ǚ椒ê灻螅蜁?huì)調(diào)用- (void)forwardInvocation:(NSInvocation *)anInvocation方法杨拐,我們可以通過(guò)anInvocation對(duì)象做很多處理祈餐,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對(duì)象等
? 第五步:若沒(méi)有實(shí)現(xiàn)- (void)forwardInvocation:(NSInvocation *)anInvocation方法哄陶,那么會(huì)進(jìn)入- (void)doesNotRecognizeSelector:(SEL)aSelector方法帆阳。若我們沒(méi)有實(shí)現(xiàn)這個(gè)方法,那么就會(huì)crash奕筐,然后提示打不到響應(yīng)的方法舱痘。到此,動(dòng)態(tài)解析的流程就結(jié)束了离赫。
runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
runtime對(duì)注冊(cè)的類會(huì)進(jìn)行布局塌碌,對(duì)于weak對(duì)象會(huì)放入一個(gè)hash表中渊胸。 用weak指向的對(duì)象內(nèi)存地址作為key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc台妆。假如weak指向的對(duì)象內(nèi)存地址是a翎猛,那么就會(huì)以a為鍵,在這個(gè) weak 表中搜索接剩,找到所有以a為鍵的weak對(duì)象切厘,從而設(shè)置為nil。
weak修飾的指針默認(rèn)值是nil(在Objective-C中向nil發(fā)送消息是安全的)
動(dòng)態(tài)綁定
- 在運(yùn)行時(shí)確定要調(diào)用的方法,動(dòng)態(tài)綁定將調(diào)用方法的確定也推遲到運(yùn)行時(shí)懊缺。在編譯時(shí)疫稿,方法的調(diào)用并不和代碼綁定在一起,只有在消實(shí)發(fā)送出來(lái)之后,才確定被調(diào)用的代碼遗座。通過(guò)動(dòng)態(tài)類型和動(dòng)態(tài)綁定技術(shù)舀凛,代碼每次執(zhí)行都可以得到不同的結(jié)果。運(yùn)行時(shí)因子負(fù)責(zé)確定消息的接收者和被調(diào)用的方法途蒋。運(yùn)行時(shí)的消息分發(fā)機(jī)制為動(dòng)態(tài)綁定提供支持猛遍。當(dāng)向一個(gè)動(dòng)態(tài)類型確定了的對(duì)象發(fā)送消息時(shí),運(yùn)行環(huán)境系統(tǒng)會(huì)通過(guò)接收者的isa指針定位對(duì)象的類号坡,并以此為起點(diǎn)確定被調(diào)用的方法懊烤,方法和消息是動(dòng)態(tài)綁定的。而且宽堆,不必在Objective-C 代碼中做任何工作奸晴,就可以自動(dòng)獲取動(dòng)態(tài)綁定的好處。在每次發(fā)送消息時(shí)日麸,特別是當(dāng)消息的接收者是動(dòng)態(tài)類型已經(jīng)確定的對(duì)象時(shí)寄啼,動(dòng)態(tài)綁定就會(huì)例行而透明地發(fā)生
文章如有問(wèn)題,請(qǐng)留言代箭,我將及時(shí)更正墩划。
滿地打滾賣萌求贊,如果本文幫助到你嗡综,輕點(diǎn)下方的紅心乙帮,給作者君增加更新的動(dòng)力。