前言
Runtime是iOS開發(fā)者進(jìn)階必須學(xué)習(xí)的一個(gè)知識(shí)點(diǎn),涵蓋面很廣疏遏,實(shí)用性也很強(qiáng)∠质梗現(xiàn)轉(zhuǎn)載了一篇很好的博文荐绝,來源于標(biāo)哥寫的技術(shù)博客一汽,感覺很不錯(cuò),希望看完后對(duì)你們有益低滩。
一、Runtime版本與平臺(tái)介紹
Runtime 有兩個(gè)版本 一個(gè)Legacy版本 岩喷,一個(gè)Modern版本恕沫。Legacy版本用于Objective-C 1,32位 的OS X的平臺(tái)上,Modern版本適用于Objective-C 2,iOS 和 OS X v10.5 及之后版本。很明顯纱意,我們現(xiàn)在采用的Runtime 為Modern版本婶溯。
二、使用Runtime的場(chǎng)景
OC程序使用Runtime 系統(tǒng)有三種情景:Objective-C Source Code偷霉、NSObject Methods迄委、Runtime Functions;
1类少、Objective-C Source Code
大部分時(shí)候runtime是在幕后運(yùn)行工作著的叙身。在編譯含有OC類、方法的代碼時(shí)硫狞,編譯器通過Runtime的消息機(jī)制在幕后完成創(chuàng)建數(shù)據(jù)信轿、調(diào)用函數(shù)。Runtime的實(shí)質(zhì)是消息的發(fā)送残吩,官方文檔稱之為Messaging财忽,翻譯成中文為消息機(jī)制。消息機(jī)制在OC源碼使用過程中會(huì)被調(diào)用泣侮。
2即彪、NSObject Methods
官方文檔原文的描述:
Some of the NSObject methods simply query the runtime system for information. These methods allow objects to perform introspection. Examples of such methods are the class method, which asks an object to identify its class; isKindOfClass: and isMemberOfClass:, which test an object’s position in the inheritance hierarchy; respondsToSelector:, which indicates whether an object can accept a particular message; conformsToProtocol:, which indicates whether an object claims to implement the methods defined in a specific protocol; and methodForSelector:, which provides the address of a method’s implementation. Methods like these give an object the ability to introspect about itself. Cocoa中絕大多數(shù)的類繼承自NSObject,因此繼承了NSObject的方法,其中一些方法可以查詢Runtime系統(tǒng)的相關(guān)信息活尊。例如: isKindOfClass: 用來判斷是否是某個(gè)類或其子類的實(shí)例isMemberOfClass: 用來判斷是否是某個(gè)類的實(shí)例 respondsToSelector: 用來判斷是否有以某個(gè)名字命名的方法(被封裝在一個(gè)selector的對(duì)象里傳遞)instancesRespondToSelector: 用來判斷實(shí)例是否有以某個(gè)名字命名的方法conformsToProtocol: 判斷是否遵循相關(guān)協(xié)議 methodForSelector: 可以獲得一個(gè)指向方法實(shí)現(xiàn)的指針隶校,并可以使用該指針直接調(diào)用方法實(shí)現(xiàn)
3琼蚯、Runtime Functions
Runtime系統(tǒng)是一個(gè)C語言動(dòng)態(tài)庫,在Xcode的/usr/include/objc里可以看到由一些列函數(shù)和數(shù)據(jù)結(jié)構(gòu)構(gòu)成的公共接口惠况,里面的許多函數(shù)可以用C語言去調(diào)用遭庶。這些函數(shù)可以在開發(fā)環(huán)境中用來生成一些工具,在一些功能場(chǎng)景中也可以用到稠屠。關(guān)于這些函數(shù)的詳細(xì)介紹可以查看Objective-C Runtime Reference
三峦睡、消息機(jī)制(Messaging)
1、objc_msgSend 函數(shù)
在OC中权埠,調(diào)用方法: [receiver message] 在編譯器里會(huì)轉(zhuǎn)換成消息機(jī)制里的消息發(fā)送形式:objc_msgSend(receiver, selector) 如果帶參數(shù)的話: objc_msgSend(receiver, selector, arg1, arg2, ...) 消息功能為動(dòng)態(tài)綁定做了很多有必要的工作: 1榨了、通過selector 在 消息接收者 class 里選擇方法實(shí)現(xiàn)(method implementation) 2、調(diào)用方法實(shí)現(xiàn)攘蔽,傳遞到接收對(duì)象 with 參數(shù) 3龙屉、傳遞方法實(shí)現(xiàn)返回值。 Note:編譯器才能啟用消息函數(shù)满俗,我們的代碼不可以转捕。 為了讓編譯器編譯時(shí),消息機(jī)制與類的結(jié)構(gòu)關(guān)聯(lián)上唆垃,每個(gè)類的結(jié)構(gòu)里添加了兩個(gè)基本的元素: 1五芝、指向父類的指針(isa指針); 2辕万、類調(diào)度表(A class dispatch table)枢步,通過Selector方法名在dispatch table里面匹配對(duì)應(yīng)的方法地址(class-specific address)。 當(dāng)一個(gè)對(duì)象被創(chuàng)建渐尿、分配內(nèi)存時(shí)醉途,它的實(shí)例里的變量會(huì)初始化,里面有一個(gè)指向它的類的結(jié)構(gòu)體的指針砖茸,isa指針隘擎。 Paste_Image.png 消息發(fā)送到一個(gè)對(duì)象時(shí),通過class結(jié)構(gòu)體里的isa指針在dispatch table里尋找相應(yīng)的 selector,如果找不到便進(jìn)入其父類里找渔彰,一直到NSObject. 一旦定位到selector 嵌屎,便調(diào)用該方法,傳遞相關(guān)數(shù)據(jù)恍涂。為了提高效率宝惰,Runtime 會(huì)緩存調(diào)用過的selector 和方法地址,在到disaptch table查找之前再沧,先到Catch里查找尼夺。
2、使用隱藏的參數(shù)
在objc_msgSend運(yùn)作過程中,在傳送消息中傳遞所有參數(shù)淤堵,包括兩個(gè)隱藏起來的參數(shù): 1寝衫、接收對(duì)象 2、方法的selector 這兩個(gè)參數(shù)為每個(gè)方法的實(shí)現(xiàn)提供調(diào)用時(shí)的相關(guān)信息拐邪,之所以說是被隱藏是因?yàn)樵诖a中不用聲明這兩個(gè)參數(shù)慰毅,沒有體現(xiàn)出這兩個(gè)參數(shù),他們被嵌入在方法的實(shí)現(xiàn)中扎阶。 一個(gè)方法中汹胃,接收對(duì)象為self,它的方法選擇器selector為_cmd,如下例子中东臀,_cmd為strange方法的selector着饥,self為接收strange方法的對(duì)象。
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
3惰赋、獲得方法地址
繞過動(dòng)態(tài)綁定的唯一方法只有獲取方法地址然后直接調(diào)用宰掉。在某些場(chǎng)景下,需要連續(xù)執(zhí)行一個(gè)方法多次赁濒,如果普通的調(diào)用會(huì)讓每次執(zhí)行該方法時(shí)相應(yīng)的消息發(fā)送內(nèi)容都重寫一次轨奄,這里我們可以使用NSObject里面一個(gè)方法methodForSelector:防止消息每次都重寫。 方法methodForSelector:可以讓指針指向方法對(duì)應(yīng)的實(shí)現(xiàn)流部,接著使用指針調(diào)用程序戚绕,執(zhí)行方法。該方法的指針必須返回適當(dāng)?shù)暮瘮?shù)類型枝冀,為了以防萬一,返回和參數(shù)類型都應(yīng)該包含進(jìn)去耘子。
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);
傳到程序(procedure)的消息中的前兩個(gè)參數(shù) 是接收對(duì)象(self)和 方法選擇器(_cmd)果漾。這兩個(gè)參數(shù)在方法語句中被隱藏了,但是當(dāng)方法作為函數(shù)被調(diào)用時(shí)必須明確存在谷誓。 使用methodForSelector:方法繞過動(dòng)態(tài)綁定可以節(jié)省很大一部分消息發(fā)送的時(shí)間绒障。但是捍歪,只有在一個(gè)特定的消息重復(fù)許多次時(shí),方法才會(huì)被簽名認(rèn)證庐镐,如上面例子中的for循環(huán)变逃。平時(shí)開發(fā)中,我們可以在一些循環(huán)中采用methodForSelector:方法提高代碼運(yùn)行效率。 Using methodForSelector: to circumvent dynamic binding saves most of the time required by messaging. However, the savings will be significant only where a particular message is repeated many times 值得注意的是methodForSelector:方法是runtime系統(tǒng)中提供得方法名眉,不是Objective-C語言自身的特性粟矿。
四、動(dòng)態(tài)方法決議
1损拢、動(dòng)態(tài)方法決議(Dynamic Method Resolution)
在一些情景下我們想要?jiǎng)討B(tài)實(shí)現(xiàn)方法陌粹,例如用@dynamic聲明屬性特征, @dynamic propertyName; 這行代碼告訴編譯器福压,與這個(gè)屬性相關(guān)的方法動(dòng)態(tài)實(shí)現(xiàn)掏秩。 我們可以通過resolveInstanceMethod: 和 resolveClassMethod: 動(dòng)態(tài)實(shí)現(xiàn)一個(gè)實(shí)例和類的selector。 一個(gè)OC方法其實(shí)是一個(gè)至少帶有self(接受對(duì)象)和 _cmd(執(zhí)行的selector)的 C語言函數(shù).我們可以通過class_addMethod:將一個(gè)函數(shù)添加成一個(gè)類中的方法隧膏,而添加的過程可以在resolveInstanceMethod:中添加
@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
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
轉(zhuǎn)發(fā)消息和動(dòng)態(tài)決議是緊密相關(guān)的哗讥。一個(gè)類在消息轉(zhuǎn)發(fā)機(jī)制(forwarding mechanism)結(jié)束之前有一個(gè)機(jī)會(huì)動(dòng)態(tài)處理一個(gè)方法,調(diào)用respondsToSelector:和instancesRespondToSelector:會(huì)先為selector提供IMP胞枕,從而可以進(jìn)行動(dòng)態(tài)方法處理杆煞。 如果你實(shí)現(xiàn)了resolveInstanceMethod:這個(gè)方法,但是想讓某些selector通過消息轉(zhuǎn)發(fā)機(jī)制轉(zhuǎn)發(fā)腐泻,在實(shí)現(xiàn)中判斷如果是這些selector 就return NO;
2决乎、動(dòng)態(tài)加載(Dynamic Loading)
OC程序可以在運(yùn)行時(shí)加載和鏈接新的類,新加入的代碼會(huì)合并進(jìn)程序中派桩,與一開始加載的類或類別同等對(duì)待构诚。 動(dòng)態(tài)加載可以用來做很多事情。例如铆惑,App的系統(tǒng)設(shè)置便是動(dòng)態(tài)加載范嘱。 在Cocoa環(huán)境中,常用于App功能的自定義定制员魏。你的程序可以在運(yùn)行時(shí)加載其他模塊丑蛤,例如 Interface Builder加載顏色,和OS X系統(tǒng)設(shè)置應(yīng)用加載偏好設(shè)置模塊撕阎。動(dòng)態(tài)加載模塊擴(kuò)展了應(yīng)用的功能受裹。 You provide the framework, but others provide the code. Runtime 提供了動(dòng)態(tài)加載的方法objc_loadModules棉饶,但是Cocoa里的NSBundle提供了更方便的接口,可以在到這里NSBundle 看看其用法岩梳。
五冀值、消息轉(zhuǎn)發(fā)
Sending a message to an object that does not handle that message is an error. However, before announcing the error, the runtime system gives the receiving object a second chance to handle the message. 當(dāng)一個(gè)對(duì)象不能正常及時(shí)處理發(fā)送過來的消息時(shí)會(huì)導(dǎo)致異常列疗,runtime系統(tǒng)在發(fā)生異常之前提供了第二次機(jī)會(huì)處理消息的機(jī)會(huì)告材。
1斥赋、消息轉(zhuǎn)發(fā)(Forwarding)
如果一個(gè)對(duì)象沒有正常處理發(fā)送過來的消息疤剑,在異常之前 Runtime 向?qū)ο蟀l(fā)送帶有 NSInvocation對(duì)象作為基礎(chǔ)參數(shù)的forwardInvocation: 消息,NSinvoction對(duì)象收納了初始消息和參數(shù)杠览。 我們可以實(shí)現(xiàn)forwardInvocation:方法做為對(duì)消息的默認(rèn)回應(yīng)管钳,防止發(fā)生異常蹋嵌。forwardInvocation:正如其名字所形容的,就是轉(zhuǎn)發(fā)消息到其他對(duì)象恋脚。 假如你想設(shè)計(jì)一個(gè)能響應(yīng)negotiate方法的對(duì)象a,而negotiate方法的實(shí)現(xiàn)是在其他對(duì)象b中书妻。你可以通過發(fā)送negotiate消息到實(shí)現(xiàn)這個(gè)方法的類b中去见间,簡(jiǎn)單地完成這個(gè)功能米诉。 更進(jìn)一步拴泌,假設(shè)你想要這個(gè)對(duì)象a能精確地(exactly)執(zhí)行negotiate方法,一個(gè)方法是繼承回季。但是它不可能在兩個(gè)類的對(duì)象中傳值茧跋。 我們可以通過實(shí)現(xiàn)一個(gè)方法簡(jiǎn)單地傳送消息到類b的實(shí)例中去“借”negotiate方法瘾杭。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
用這個(gè)方法會(huì)帶來一些麻煩,對(duì)于每一個(gè)你想"借"的方法讨阻,你需要實(shí)現(xiàn)一個(gè)方法去獲得篡殷,而且對(duì)于一些未知的方法奇瘦,你無法去處理耳标。 通過forwardInvocation:方法可以解決這個(gè)問題呼猪,這里我們可以采用動(dòng)態(tài)的方式而不是靜態(tài)宋距。主要工作過程為:當(dāng)一個(gè)對(duì)象因?yàn)闆]有匹配的selector方法而不能響應(yīng)一個(gè)消息時(shí),runtime系統(tǒng)向這個(gè)對(duì)象發(fā)送forwardInvocation:消息摊腋,每一個(gè)對(duì)象都從NSObject繼承了forwardInvocation:方法视粮,但是在NSObject中蕾殴,這個(gè)方法結(jié)束后直接調(diào)用了doesNotRecognizeSelector:。我們必須自己重寫forwardInvocation:方法執(zhí)行之后相關(guān)的實(shí)現(xiàn)。這樣我們就可以利用forwardInvocation:方法轉(zhuǎn)發(fā)消息到其他類中去批幌。 我的github這里面的Messaging文件是消息轉(zhuǎn)發(fā)相關(guān)例子,注釋中有進(jìn)行了說明荧缘。 轉(zhuǎn)發(fā)一個(gè)消息時(shí),forwardInvocation:方法中需要做到: 1桐愉、確定消息發(fā)送到何處。 2靡羡、發(fā)送時(shí)帶上原始參數(shù)描扯。 消息可以通過invokeWithTarget:方法發(fā)送
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
返回值類型可以被傳回到原始的傳送者(sender)中,包括 id類型恩够,結(jié)構(gòu)體蜂桶,雙精度浮點(diǎn)數(shù)。 可以把forwardInvocation:方法當(dāng)做無法辨認(rèn)的消息的分發(fā)中心也切,將消息分配給各個(gè)接收者扑媚,它可以把所有消息發(fā)送到同一個(gè)目的地,也可以聯(lián)合幾個(gè)消息做出同一個(gè)響應(yīng)雷恃。forwardInvocation:方法主要面向方法實(shí)現(xiàn)疆股,但是它提供的通過消息轉(zhuǎn)發(fā)鏈鏈接對(duì)象的機(jī)會(huì)為程序設(shè)計(jì)帶來更多可能性。 Note: forwardInvocation: 只有在調(diào)用了不存在的方法導(dǎo)致消息無法處理時(shí)才會(huì)被調(diào)用倒槐。 可以到Foundation框架中查看NSInvocation的相關(guān)文檔旬痹,獲取更多的詳細(xì)內(nèi)容唱凯。
2、消息轉(zhuǎn)發(fā)與多繼承(Forwarding and Multiple Inheritance)
消息轉(zhuǎn)發(fā)參照了繼承,在OC程序中可以借用一些多繼承的功能吩翻。 在下圖中弧轧,一個(gè)對(duì)象對(duì)一個(gè)消息做出回應(yīng),類似于借來一個(gè)在其他類定義實(shí)現(xiàn)的方法。 Paste_Image.png 在這個(gè)插圖中毒涧,warrior實(shí)例轉(zhuǎn)發(fā)了一個(gè)negotiate消息到Diplomat實(shí)例中,執(zhí)行Diplomat中的negotiate方法傅物,結(jié)果看起來像是warrior實(shí)例執(zhí)行了一個(gè)和Diplomat實(shí)例一樣的negotiate方法贮缅,其實(shí)執(zhí)行者還是Diplomat實(shí)例永淌。 在上面的例子中,看起來相當(dāng)于Warrior類繼承了Diplomat芹助。 The object that forwards a message thus “inherits” methods from two branches of the inheritance hierarchy—its own branch and that of the object that responds to the message 消息轉(zhuǎn)發(fā)提供了許多類似于多繼承的特性,但是他們之間有一個(gè)很大的不同: 多繼承:合并了不同的行為特征在一個(gè)單獨(dú)的對(duì)象中琉朽,會(huì)得到一個(gè)重量級(jí)多層面的對(duì)象。 消息轉(zhuǎn)發(fā):將各個(gè)功能分散到不同的對(duì)象中若河,得到的一些輕量級(jí)的對(duì)象,這些對(duì)象通過消息通過消息轉(zhuǎn)發(fā)聯(lián)合起來。
3击纬、代理對(duì)象(Surrogate Objects)
消息轉(zhuǎn)發(fā) 不僅參照了多繼承,它還讓用輕量級(jí)對(duì)象代替重量級(jí)對(duì)象成為了可能。 通過代理(Surrogate)可以為對(duì)象篩選消息负敏。 代理管理發(fā)送到接收者的消息,確定參數(shù)值被復(fù)制柬焕,拯救等等雀鹃。但是它不企圖去做很多其他的,它不重復(fù)對(duì)象的功能只是簡(jiǎn)單地提供對(duì)象一個(gè)可以接收來自其他應(yīng)用消息的地址励两。 舉個(gè)例子黎茎,有一個(gè)重量級(jí)對(duì)象,里面加入了許多大型數(shù)據(jù)当悔,如圖片視頻等傅瞻,每次使用這個(gè)對(duì)象的時(shí)候都需要讀取磁盤上的內(nèi)容,需要消耗很多時(shí)間(time-consuming)盲憎,所以我們更偏向于采用懶加載模式嗅骄。 在這樣的情況下,你可以初始化一個(gè)簡(jiǎn)單的輕量級(jí)對(duì)象來代理(surrogate)它焙畔。利用代理對(duì)象可以做到例如查詢數(shù)據(jù)信息等掸读,而不用加載一整個(gè)重量級(jí)對(duì)象。如果是直接用重量級(jí)對(duì)象的話,它會(huì)一直被持有占用資源儿惫。當(dāng)代理的forwardInvocation:方法第一次接收消息的時(shí)候澡罚,它會(huì)確保對(duì)象是否存在,如果不存在邊創(chuàng)建一個(gè)肾请。 當(dāng)這個(gè)代理對(duì)象發(fā)送的消息覆蓋了這個(gè)重量級(jí)對(duì)象的所有功能時(shí)留搔,這個(gè)代理對(duì)象就相當(dāng)于和重量級(jí)對(duì)象一樣。 創(chuàng)建一個(gè)輕量級(jí)的對(duì)象來代理一個(gè)重量級(jí)對(duì)象铛铁,完成相對(duì)應(yīng)的功能隔显,而不用一直持有著重量級(jí)對(duì)象,從而可以減少資源占用饵逐。
4括眠、消息轉(zhuǎn)發(fā)與繼承(Forwarding and Inheritance)
盡管消息轉(zhuǎn)發(fā)參照了繼承,但是NSObject 不會(huì)混亂 像 respondsToSelector: 和 isKindOfClass: 這些方法只有在繼承體系里看到倍权,不會(huì)出現(xiàn)在消息轉(zhuǎn)發(fā)鏈里掷豺。 例如, Warrio 對(duì)象是否響應(yīng)negotiate
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
大多情況下答案是No,盡管它能無錯(cuò)誤地收到negotiate消息或者某種意義上通過轉(zhuǎn)發(fā)到Diplomat來響應(yīng)薄声。 如果我們想通過消息轉(zhuǎn)發(fā)設(shè)立一個(gè)代理對(duì)象或擴(kuò)展類的功能当船,消息轉(zhuǎn)發(fā)體質(zhì)就得像繼承一樣清晰顯然。如果我們想讓對(duì)象看起來真正地繼承了父類對(duì)象的行為特征默辨,我們需要去重寫respondsToSelector: 和isKindOfClass:方法德频。
- (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:兩個(gè)方法外,instancesRespondToSelector方法也映射著消息轉(zhuǎn)發(fā)規(guī)則缩幸。當(dāng)涉及到協(xié)議時(shí)我們需要還要考慮conformsToProtocol:壹置。同樣,一個(gè)對(duì)象轉(zhuǎn)發(fā)消息時(shí)桌粉,將會(huì)執(zhí)行methodSignatureForSelector:蒸绩,它描述了方法的簽名認(rèn)證等相關(guān)信息,如果一個(gè)對(duì)象能夠轉(zhuǎn)發(fā)詳細(xì)到它的代理铃肯,我們需要實(shí)現(xiàn)methodSignatureForSelector:方法患亿。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
注意:這是一個(gè)高級(jí)用法,只適用于沒有其他可能解決方案的情況下使用押逼,不建議用它來代替繼承步藕。當(dāng)你使用這個(gè)方法的時(shí)候必須保證你完全熟悉類相關(guān)的行為特征以及消息轉(zhuǎn)發(fā)的情況。 里面涉及的一些方法可以到NSObject 和NSInvocation中去查閱更詳細(xì)的內(nèi)容
六挑格、Type Encodings
為了協(xié)助runtime 系統(tǒng)咙冗,編譯器為每個(gè)方法用字符串 編碼 返回和參數(shù) 類型,并將字符串與方法選擇器相關(guān)聯(lián)漂彤。所使用的編碼表同樣在其他context里是有用的雾消,所以它是公共可用的with@encode()編譯程序指令灾搏。提供一個(gè)類型說明時(shí),@encode()返回一個(gè)編碼這個(gè)類型的字符串×⑷螅可以是基礎(chǔ)類型狂窑,int, 指針,結(jié)構(gòu)體桑腮,聯(lián)合體泉哈。或一個(gè)類破讨,事實(shí)上丛晦,可以用來做C語言sizeof()操作的參數(shù)
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
下面是類型編碼表。里面有許多是與archive 和 distribution 編碼時(shí)重復(fù)的編碼提陶,但是 可以到NSCoder查閱更詳細(xì)的內(nèi)容 Objective_C type encodings
重要:OC不支持long double 類型烫沙,@encode(long double)將會(huì)返回d,即double類型;
數(shù)組的類型編碼是在方括號(hào)里面的包含一個(gè)代表元素個(gè)數(shù)的數(shù)字和一個(gè)數(shù)組元素的類型編碼隙笆,例如一個(gè)包含12個(gè)浮點(diǎn)數(shù)的數(shù)組:
[12^f]
結(jié)構(gòu)體類型編碼的顯示是一個(gè)大括號(hào)里面包含名稱和變量的類型編碼斧吐,例如:
typedef struct example {
id anObject;
char *aString;
int anInt;
} Example;
這個(gè)結(jié)構(gòu)體的類型編碼是:
{example=@*i}
指向這個(gè)結(jié)構(gòu)體的結(jié)構(gòu)體指針類型編碼為:
^{example=@*i}
還有另外一種去除了內(nèi)部的說明:
^^{example}
對(duì)象(Object)與結(jié)構(gòu)體類似,例如NSObject的編碼:
{NSObject=#}
NSObject只聲明一個(gè)實(shí)例變量:isa,它是一個(gè)Class仲器。 該注意的是,盡管@encode()指令不返回這些編碼仰冠,但是當(dāng)他們?cè)趨f(xié)議中聲明的方法中被使用到時(shí)乏冀,runtime系統(tǒng)為類型限定符提供了另外的編碼,如下圖洋只。 Objective-C method encodings
七辆沦、聲明屬性(Declared Properties)
編譯器在屬性聲明(Property declarations)的時(shí)候,它生成與類识虚、類別肢扯、協(xié)議相關(guān)聯(lián)的元數(shù)據(jù),我們可以通過這些元數(shù)據(jù)使用這些函數(shù):通過名字查看屬性担锤、得到屬性的類型(@encode串形式)蔚晨、復(fù)制出屬性的相關(guān)參數(shù)(C語言字符串形式)列表等。
1肛循、屬性類型和函數(shù)(Property Type and Functions)
可以使用class_copyPropertyList和 protocol_copyPropertyList 獲取屬性數(shù)組
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
例如:有這么一個(gè)類如下:
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
可以獲得其屬性隊(duì)列:
id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
獲取屬性名稱:
const char *property_getName(objc_property_t property)
可以通過一個(gè)已知名字的Class或協(xié)議獲取屬性
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)
可以獲取屬性的相關(guān)特性铭腕,特性中包括了許多信息。例如類型編碼字符串等多糠,下面的章節(jié)中有具體講解累舷。
const char *property_getAttributes(objc_property_t property)
將以上的函數(shù)結(jié)合在一起,可以打印出類中所有的屬性的信息:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}
2、屬性類型字符串(Property Type String)
可以用property_getAttributes函數(shù)得到屬性的名字夹孔、類型編碼字符串被盈、以及特性析孽。 字符串以T開頭,后面接著@encode類型編碼只怎,接著是逗號(hào)袜瞬,接著是V,接著是屬性名,在這中間尝盼,使用下面這個(gè)表中的符號(hào)吞滞,用逗號(hào)隔開。 Declared property type encodings.png 具體例子看下面的屬性特性例子盾沫。
3裁赠、屬性特性(Property Attribute)例子
先預(yù)處理:
enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };
下面是屬性類型編程字符串的例子: Paste_Image.png Paste_Image.png
常見使用:
runtime 常見的使用有: 動(dòng)態(tài)交換兩個(gè)方法的實(shí)現(xiàn) 實(shí)現(xiàn)分類也可以添加屬性 實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔 實(shí)現(xiàn)字典轉(zhuǎn)模型的自動(dòng)轉(zhuǎn)換 Hook 這里是代碼完整版WXSRuntime
動(dòng)態(tài)交換兩個(gè)方法的實(shí)現(xiàn)
``` NSLog(@"------Normal-----\n"); ShowExchange *normarlTest = [ShowExchange new]; [normarlTest firstMethod]; NSLog(@"------Normal-----\n");
//交換實(shí)例方法
NSLog(@"------exchange-----\n");
Method m1 = class_getInstanceMethod([ShowExchange class], @selector(firstMethod));
Method m2 = class_getInstanceMethod([ShowExchange class], @selector(secondMethod));
method_exchangeImplementations(m1, m2);
ShowExchange *test = [ShowExchange new];
[test firstMethod];
NSLog(@"------exchange InstanceMethod-----\n");
##實(shí)現(xiàn)分類也可以添加屬性
-(void)setWxsTitle:(NSString )wxsTitle { objc_setAssociatedObject(self, WXSAddPropertyKeyTitle, wxsTitle, OBJC_ASSOCIATION_RETAIN); } -(NSString )wxsTitle { return objc_getAssociatedObject(self, WXSAddPropertyKeyTitle); }
##實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔
unsigned int outCount = 0; Ivar ivars = class_copyIvarList(self.class, &outCount); for (int i = 0; i< outCount; i++) { Ivar ivar = ivars[i]; const char ivarName = ivar_getName(ivar); NSString ivarNameStr = [NSString stringWithUTF8String:ivarName]; NSString setterName = [ivarNameStr substringFromIndex:1];
//解碼
id obj = [aDecoder decodeObjectForKey:setterName]; //要注意key與編碼的key是一致的
SEL setterSel = [self creatSetterWithKey:setterName];
if (obj) {
((void (*)(id ,SEL ,id))objc_msgSend)(self,setterSel,obj);
}
}
free(ivars);
##實(shí)現(xiàn)字典轉(zhuǎn)模型的自動(dòng)轉(zhuǎn)換
unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *key = [NSString stringWithUTF8String:propertyName];
id value = nil;
if (![dict[key] isKindOfClass:[NSNull class]]) {
value = dict[key];
}
unsigned int count = 0;
objc_property_attribute_t *atts = property_copyAttributeList(property, &count);
objc_property_attribute_t att = atts[0];
NSString *type = [NSString stringWithUTF8String:att.value];
type = [type stringByReplacingOccurrencesOfString:@"“" withString:@""];
type = [type stringByReplacingOccurrencesOfString:@"@" withString:@""];
NSLog(@"type%@",type);
//數(shù)據(jù)為數(shù)組時(shí)
if ([value isKindOfClass:[NSArray class]]) {
Class class = NSClassFromString(key);
NSMutableArray *temArr = [[NSMutableArray alloc] init];
for (NSDictionary *tempDic in value) {
if (class) {
id model = [[class alloc] initWithDic:tempDic];
[temArr addObject:model];
}
}
value = temArr;
}
//數(shù)據(jù)為字典時(shí)
if ([value isKindOfClass:[NSDictionary class]] && ![type hasPrefix:@"NS"] ) {
Class class = NSClassFromString(key);
if (class) {
value = [[class alloc] initWithDic:value];
}
}
// 賦值 SEL setterSel = [self creatSetterWithKey:key]; if (setterSel != nil) { ((void (*)(id,SEL,id))objc_msgSend)(self,setterSel,value); }
}
## Hook
(void)viewDidLoad { [super viewDidLoad]; Method m1 = class_getInstanceMethod([self class], @selector(viewWillAppear:)); Method m2 = class_getInstanceMethod([self class], @selector(wxs_viewWillAppear:));
BOOL isSuccess = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(m2), method_getTypeEncoding(m2)); if (isSuccess) {
// 添加成功:說明源方法m1現(xiàn)在的實(shí)現(xiàn)為交換方法m2的實(shí)現(xiàn),現(xiàn)在將源方法m1的實(shí)現(xiàn)替換到交換方法m2中
class_replaceMethod([self class], @selector(wxs_viewWillAppear:), method_getImplementation(m1), method_getTypeEncoding(m1));
}else {
//添加失敻熬:說明源方法已經(jīng)有實(shí)現(xiàn)佩捞,直接將兩個(gè)方法的實(shí)現(xiàn)交換即
method_exchangeImplementations(m1, m2);
} } -(void)viewWillAppear:(BOOL)animated { NSLog(@"viewWillAppear"); }
(void)wxs_viewWillAppear:(BOOL)animated { NSLog(@"Hook : 攔截到viewwillApear的實(shí)現(xiàn),在其基礎(chǔ)上添加了這行代碼"); [self wxs_viewWillAppear:YES];
} ```
######歡迎加群192699811討論蕾哟,相互學(xué)習(xí)