2018-08-29

Runtime 理解

Runtime 又叫運(yùn)行時(shí)怒炸,是一套底層的C語(yǔ)言API宦搬,其為iOS內(nèi)部的核心之一牙瓢,我們平時(shí)編寫的OC代碼,底層都是基于它來(lái)實(shí)現(xiàn)的间校,例如:

以上你可能看不出它的價(jià)值矾克,但是我們需要了解的是 Objective-C 是一門動(dòng)態(tài)語(yǔ)言,它會(huì)將一些工作放在代碼運(yùn)行時(shí)才處理而并非編譯時(shí)憔足。也就是說胁附,有很多類和成員變量在我們編譯的時(shí)是不知道的,而在運(yùn)行時(shí)滓彰,我們所編寫的代碼會(huì)轉(zhuǎn)換成完整的確定的代碼運(yùn)行控妻。

因此,編譯器是不夠的找蜜,我們還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(Runtime system)來(lái)處理編譯后的代碼饼暑。

Runtime 基本是用 C 和匯編寫的,由此可見蘋果為了動(dòng)態(tài)系統(tǒng)的高效而做出的努力洗做。蘋果和 GNU 各自維護(hù)一個(gè)開源的 Runtime 版本弓叛,這兩個(gè)版本之間都在努力保持一致。

?https://opensource.apple.com/source/objc4/?下載蘋果維護(hù)的開源代碼诚纸。

Runtime 的作用

Objc 在三種層面上與 Runtime 系統(tǒng)進(jìn)行交互:

通過 Objective-C 源代碼

通過 Foundation 框架的 NSObject 類定義的方法

通過對(duì) Runtime 庫(kù)函數(shù)的直接調(diào)用

Objective-C 源代碼

多數(shù)情況我們只需要編寫 OC 代碼即可撰筷,Runtime 系統(tǒng)自動(dòng)在幕后搞定一切,還記得簡(jiǎn)介中如果我們調(diào)用方法畦徘,編譯器會(huì)將 OC 代碼轉(zhuǎn)換成運(yùn)行時(shí)代碼毕籽,在運(yùn)行時(shí)確定數(shù)據(jù)結(jié)構(gòu)和函數(shù)抬闯。

通過 Foundation 框架的 NSObject 類定義的方法

Cocoa 程序中絕大部分類都是 NSObject 類的子類,所以都繼承了 NSObject 的行為关筒。(NSProxy 類時(shí)個(gè)例外溶握,它是個(gè)抽象超類)

一些情況下,NSObject 類僅僅定義了完成某件事情的模板蒸播,并沒有提供所需要的代碼睡榆。例如?-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)的地址。

通過對(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ù)。對(duì)于公共接口都有哪些搭独,后面會(huì)講到婴削。我將會(huì)參考蘋果官方的 API 文檔。


一些 Runtime 的術(shù)語(yǔ)的數(shù)據(jù)結(jié)構(gòu)

要想全面了解 Runtime 機(jī)制牙肝,我們必須先了解 Runtime 的一些術(shù)語(yǔ)唉俗,他們都對(duì)應(yīng)著數(shù)據(jù)結(jié)構(gòu)。

SEL

它是selector在 Objc 中的表示(Swift 中是 Selector 類)配椭。selector 是方法選擇器虫溜,其實(shí)作用就和名字一樣,日常生活中股缸,我們通過人名辨別誰(shuí)是誰(shuí)衡楞,注意 Objc 在相同的類中不會(huì)有命名相同的兩個(gè)方法。selector 對(duì)方法名進(jìn)行包裝敦姻,以便找到對(duì)應(yīng)的方法實(shí)現(xiàn)瘾境。它的數(shù)據(jù)結(jié)構(gòu)是:

typedefstruct objc_selector *SEL;

我們可以看出它是個(gè)映射到方法的 C 字符串歧杏,你可以通過 Objc 編譯器器命令@selector()?或者 Runtime 系統(tǒng)的?sel_registerName?函數(shù)來(lái)獲取一個(gè)?SEL?類型的方法選擇器。

注意:

不同類中相同名字的方法所對(duì)應(yīng)的 selector 是相同的迷守,由于變量的類型不同犬绒,所以不會(huì)導(dǎo)致它們調(diào)用方法實(shí)現(xiàn)混亂。

id

id 是一個(gè)參數(shù)類型盒犹,它是指向某個(gè)類的實(shí)例的指針懂更。定義如下:

typedefstruct 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í)類型聘殖,詳見:KVO章節(jié)晨雳。

Class

typedefstruct 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;constchar *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;

}

由此可見讹蘑,我們可以動(dòng)態(tài)修改?*methodList?的值來(lái)添加成員方法末盔,這也是 Category 實(shí)現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因座慰。這里可以參考下美團(tuán)技術(shù)團(tuán)隊(duì)的文章:深入理解 Objective-C: Category??https://tech.meituan.com/DiveIntoCategory.html陨舱。

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 指針,這說明 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)用捂敌。


上圖實(shí)現(xiàn)是?super_class?指針艾扮,虛線時(shí)?isa?指針。而根元類的父類是?NSObject占婉,isa指向了自己泡嘴。而?NSObject?沒有父類。

最后?objc_class?中還有一個(gè)?objc_cache?逆济,緩存酌予,它的作用很重要,后面會(huì)提到纹腌。

Method

Method 代表類中某個(gè)方法的類型

typedefstruct 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?是表示成員變量的類型莱褒。

typedefstruct 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é)

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)肯定是唯一的,通過一組?id和?SEL?參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址枫吧。

而一個(gè)確定的方法也只有唯一的一組?id?和?SEL?參數(shù)浦旱。

Cache

Cache 定義如下:

typedefstruct objc_cache *Cachestruct objc_cache {unsignedint mask/* total = mask + 1 */? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;unsignedint 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 繞過主存先訪問 Cache 一樣。

Property

typedefstruct objc_property *Property;typedefstruct objc_property *objc_property_t;//這個(gè)更常用

可以通過class_copyPropertyList?和?protocol_copyPropertyList?方法獲取類和協(xié)議中的屬性:

objc_property_t *class_copyPropertyList(Class cls,unsignedint *outCount)objc_property_t *protocol_copyPropertyList(Protocol *proto,unsignedint *outCount)

注意:

返回的是屬性列表鹿响,列表中每個(gè)元素都是一個(gè)?objc_property_t?指針

#import@interfacePerson :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é)果如下:

2014-11-1011:27:28.473 test[2321:451525]32014-11-1011:27:28.473 test[2321:451525] name--------T@"NSString",&,N,V_name2014-11-1011:27:28.473 test[2321:451525] age--------Ti,N,V_age2014-11-1011:27:28.474 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,constchar *name)objc_property_t protocol_getProperty(Protocol *proto,constchar *name,BOOL isRequiredProperty,BOOL isInstanceProperty)

class_getProperty?和?protocol_getProperty?通過給出屬性名在類和協(xié)議中獲得屬性的引用毅哗。


消息

一些 Runtime 術(shù)語(yǔ)講完了听怕,接下來(lái)就要說到消息了。體會(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ì)被忽略掉。

如果上面兩步都通過了谁榜,那么就開始查找這個(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ù)

疑問:

我們經(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方法的定義中木人,我們并沒有發(fā)現(xiàn)這兩個(gè)參數(shù)的聲明信柿。它們時(shí)在代碼被編譯時(shí)被插入方法實(shí)現(xiàn)中的。盡管這些參數(shù)沒有被明確聲明醒第,在源代碼中我們?nèi)匀豢梢砸盟鼈儭?/p>

這兩個(gè)參數(shù)中渔嚷,?self更實(shí)用。它是在方法實(shí)現(xià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; Classclass; };

這個(gè)結(jié)構(gòu)體指明了消息應(yīng)該被傳遞給特定的父類。?receiver?仍然是?self?本身漠吻,當(dāng)我們想通過?[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í)气嫁,我們可以通過分別重載?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(idself, SEL _cmd) {// implementation ....}@implementationMyClass+ (BOOL)resolveInstanceMethod:(SEL)aSEL{if (aSEL ==@selector(resolveThisMethodDynamically)) {? ? ? ? ? class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP,"v@:");returnYES;? ? }return [super resolveInstanceMethod:aSEL];}@end

上面的例子為?resolveThisMethodDynamically?方法添加了實(shí)現(xiàn)內(nèi)容崖面,就是?dynamicMethodIMP?方法中的代碼。其中?"v@:"?表示返回值和參數(shù)梯影,這個(gè)符號(hào)表示的含義見: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ā)

重定向

消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)允許我們替換消息的接收者為其他對(duì)象。通過?- (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ā)送消息颈走。

轉(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ì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無(wú)法相應(yīng)某消息時(shí)颅围,運(yùn)行時(shí)系統(tǒng)將通過?forwardInvocation:?消息通知該對(duì)象伟葫。每個(gè)對(duì)象都繼承了?forwardInvocation:?方法。但是院促,?NSObject?中的方法實(shí)現(xiàn)只是簡(jiǎn)單的調(diào)用了?doesNotRecognizeSelector:筏养。通過實(shí)現(xiàn)自己的?forwardInvocation:?方法,我們可以將消息轉(zhuǎn)發(fā)給其他對(duì)象常拓。

forwardInvocation:?方法就是一個(gè)不能識(shí)別消息的分發(fā)中心渐溶,將這些不能識(shí)別的消息轉(zhuǎn)發(fā)給不同的接收對(duì)象,或者轉(zhuǎn)發(fā)給同一個(gè)對(duì)象弄抬,再或者將消息翻譯成另外的消息茎辐,亦或者簡(jiǎn)單的“吃掉”某些消息,因此沒有響應(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ì)象中的方法接過來(lái)或者“繼承”過來(lái)一樣。

這使得在不同繼承體系分支下的兩個(gè)類可以實(shí)現(xiàn)“繼承”對(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ù)雜。

轉(zhuǎn)發(fā)與繼承

雖然轉(zhuǎn)發(fā)可以實(shí)現(xiàn)繼承的功能爬范,但是?NSObject?還是必須表面上很嚴(yán)謹(jǐn)父腕,像?respondsToSelector:?和?isKindOfClass:?這類方法只會(huì)考慮繼承體系,不會(huì)考慮轉(zhuǎn)發(fā)鏈青瀑。

如果上圖中的?Warrior?對(duì)象被問到是否能響應(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] )returnYES;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.? */? ? }returnNO;

}

除了?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)存布局就形成了峭火,它表明訪問類的實(shí)例變量的位置毁习。實(shí)例變量一次根據(jù)自己所占空間而產(chǎn)生位移:

上圖左是?NSObject?類的實(shí)例變量布局。右邊是我們寫的類的布局卖丸。這樣子有一個(gè)很大的缺陷纺且,就是缺乏拓展性。哪天蘋果更新了?NSObject?類的話稍浆,就會(huì)出現(xiàn)問題:
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末载碌,一起剝皮案震驚了整個(gè)濱河市猜嘱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫁艇,老刑警劉巖朗伶,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異步咪,居然都是意外死亡论皆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門猾漫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)点晴,“玉大人,你說我怎么就攤上這事悯周×6剑” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵禽翼,是天一觀的道長(zhǎng)屠橄。 經(jīng)常有香客問我,道長(zhǎng)捐康,這世上最難降的妖魔是什么仇矾? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮解总,結(jié)果婚禮上贮匕,老公的妹妹穿的比我還像新娘。我一直安慰自己花枫,他們只是感情好刻盐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著劳翰,像睡著了一般敦锌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佳簸,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天乙墙,我揣著相機(jī)與錄音,去河邊找鬼生均。 笑死听想,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的马胧。 我是一名探鬼主播汉买,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佩脊!你這毒婦竟也來(lái)了蛙粘?” 一聲冷哼從身側(cè)響起垫卤,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎出牧,沒想到半個(gè)月后穴肘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崔列,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年梢褐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赵讯。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盈咳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出边翼,到底是詐尸還是另有隱情鱼响,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布组底,位于F島的核電站丈积,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏债鸡。R本人自食惡果不足惜江滨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厌均。 院中可真熱鬧唬滑,春花似錦、人聲如沸棺弊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)模她。三九已至稻艰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侈净,已是汗流浹背尊勿。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留畜侦,地道東北人元扔。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像夏伊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吻氧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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