前言
Runtime是iOS開發(fā)者進階必須學(xué)習(xí)的一個知識點。網(wǎng)上關(guān)于Runtime 有許多介紹,有深入有簡單介紹,也有實際應(yīng)用舉例,但是都不夠系統(tǒng),相關(guān)的知識點得不到關(guān)聯(lián),對runtime 的認知不能形成一個體系。這里參照蘋果官方文檔,加上自己的一些理解,進行了系統(tǒng)的介紹總結(jié)。
文章篇幅很長,前面很大一部分是概念介紹有點枯燥,如果想直接學(xué)習(xí)runtime的使用可以直接查看后面的常見使用介紹,或者下載我寫的Demo:WXSRuntime 歡迎star 下載。
(歡迎轉(zhuǎn)載,轉(zhuǎn)載注明出處)
目錄
一、Runtime版本與平臺介紹
二、使用Runtime的場景
1、Objective-C Source Code
2、NSObject Methods
3、Runtime Functions
三、消息機制(Messaging)
1、objc_msgSend 函數(shù)
2、使用隱藏的參數(shù)
3、獲得方法地址
四、動態(tài)方法決議
1、動態(tài)方法決議(Dynamic Method Resolution)
2、動態(tài)加載(Dynamic Loading)
五、消息轉(zhuǎn)發(fā)
1、消息轉(zhuǎn)發(fā)(Forwarding)
2、消息轉(zhuǎn)發(fā)與多繼承(Forwarding and Multiple Inheritance)
3、代理對象(Surrogate Objects)
4、消息轉(zhuǎn)發(fā)與繼承(Forwarding and Inheritance)
六、Type Encodings
七、聲明屬性(Declared Properties)
1、屬性類型和函數(shù)(Property Type and Functions)
2、屬性類型字符串(Property Type String)
3、屬性特性(Property Attribute)例子
正文
一、Runtime版本與平臺介紹
Runtime 有兩個版本 一個Legacy版本 ,一個Modern版本。Legacy版本用于Objective-C 1,32位 的OS X的平臺上,Modern版本適用于Objective-C 2,iOS 和 OS X v10.5 及之后版本。很明顯,我們現(xiàn)在采用的Runtime 為Modern版本。
二、使用Runtime的場景
OC程序使用Runtime 系統(tǒng)有三種情景:Objective-C Source Code、NSObject Methods、Runtime Functions;
1、Objective-C Source Code
大部分時候runtime是在幕后運行工作著的。在編譯含有OC類、方法的代碼時,編譯器通過Runtime的消息機制在幕后完成創(chuàng)建數(shù)據(jù)、調(diào)用函數(shù)。Runtime的實質(zhì)是消息的發(fā)送,官方文檔稱之為Messaging,翻譯成中文為消息機制。消息機制在OC源碼使用過程中會被調(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:
用來判斷是否是某個類或其子類的實例
isMemberOfClass:
用來判斷是否是某個類的實例
respondsToSelector:
用來判斷是否有以某個名字命名的方法(被封裝在一個selector的對象里傳遞)
instancesRespondToSelector:
用來判斷實例是否有以某個名字命名的方法
conformsToProtocol:
判斷是否遵循相關(guān)協(xié)議
methodForSelector:
可以獲得一個指向方法實現(xiàn)的指針,并可以使用該指針直接調(diào)用方法實現(xiàn)
3、Runtime Functions
Runtime系統(tǒng)是一個C語言動態(tài)庫,在Xcode的/usr/include/objc里可以看到由一些列函數(shù)和數(shù)據(jù)結(jié)構(gòu)構(gòu)成的公共接口,里面的許多函數(shù)可以用C語言去調(diào)用。這些函數(shù)可以在開發(fā)環(huán)境中用來生成一些工具,在一些功能場景中也可以用到。關(guān)于這些函數(shù)的詳細介紹可以查看Objective-C Runtime Reference
三、消息機制(Messaging)
1、objc_msgSend 函數(shù)
在OC中,調(diào)用方法:
[receiver message]
在編譯器里會轉(zhuǎn)換成消息機制里的消息發(fā)送形式:
objc_msgSend(receiver, selector)
如果帶參數(shù)的話:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息功能為動態(tài)綁定做了很多有必要的工作:
1、通過selector 在 消息接收者 class 里選擇方法實現(xiàn)(method implementation)
2、調(diào)用方法實現(xiàn),傳遞到接收對象 with 參數(shù)
3、傳遞方法實現(xiàn)返回值。
Note:編譯器才能啟用消息函數(shù),我們的代碼不可以。
為了讓編譯器編譯時,消息機制與類的結(jié)構(gòu)關(guān)聯(lián)上,每個類的結(jié)構(gòu)里添加了兩個基本的元素:
1、 指向父類的指針(isa指針);
2、 類調(diào)度表(A class dispatch table),通過Selector方法名在dispatch table里面匹配對應(yīng)的方法地址(class-specific address)。
當(dāng)一個對象被創(chuàng)建、分配內(nèi)存時,它的實例里的變量會初始化,里面有一個指向它的類的結(jié)構(gòu)體的指針,isa指針。
消息發(fā)送到一個對象時,通過class結(jié)構(gòu)體里的isa指針在dispatch table里尋找相應(yīng)的 selector,如果找不到便進入其父類里找,一直到NSObject. 一旦定位到selector ,便調(diào)用該方法,傳遞相關(guān)數(shù)據(jù)。為了提高效率,Runtime 會緩存調(diào)用過的selector 和方法地址,在到disaptch table查找之前,先到cache里查找。
2、使用隱藏的參數(shù)
在objc_msgSend
運作過程中,在傳送消息中傳遞所有參數(shù),包括兩個隱藏起來的參數(shù):
1、 接收對象
2、 方法的selector
這兩個參數(shù)為每個方法的實現(xiàn)提供調(diào)用時的相關(guān)信息,之所以說是被隱藏是因為在代碼中不用聲明這兩個參數(shù),沒有體現(xiàn)出這兩個參數(shù),他們被嵌入在方法的實現(xiàn)中。
一個方法中,接收對象為self
,它的方法選擇器selector為_cmd
,如下例子中,_cmd
為strange
方法的selector,self
為接收strange
方法的對象。
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
3、獲得方法地址
繞過動態(tài)綁定的唯一方法只有獲取方法地址然后直接調(diào)用。在某些場景下,需要連續(xù)執(zhí)行一個方法多次,如果普通的調(diào)用會讓每次執(zhí)行該方法時相應(yīng)的消息發(fā)送內(nèi)容都重寫一次,這里我們可以使用NSObject里面一個方法methodForSelector:
防止消息每次都重寫。
方法methodForSelector:
可以讓指針指向方法對應(yīng)的實現(xiàn),接著使用指針調(diào)用程序,執(zhí)行方法。該方法的指針必須返回適當(dāng)?shù)暮瘮?shù)類型,為了以防萬一,返回和參數(shù)類型都應(yīng)該包含進去。
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)的消息中的前兩個參數(shù) 是接收對象(self)和 方法選擇器(_cmd)。這兩個參數(shù)在方法語句中被隱藏了,但是當(dāng)方法作為函數(shù)被調(diào)用時必須明確存在。
使用methodForSelector:
方法繞過動態(tài)綁定可以節(jié)省很大一部分消息發(fā)送的時間。但是,只有在一個特定的消息重復(fù)許多次時,方法才會被簽名認證,如上面例子中的for循環(huán)。平時開發(fā)中,我們可以在一些循環(huán)中采用methodForSelector:
方法提高代碼運行效率。
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語言自身的特性。
四、動態(tài)方法決議
1、動態(tài)方法決議(Dynamic Method Resolution)
在一些情景下我們想要動態(tài)實現(xiàn)方法,例如用@dynamic聲明屬性特征,
@dynamic propertyName;
這行代碼告訴編譯器,與這個屬性相關(guān)的方法動態(tài)實現(xiàn)。
我們可以通過resolveInstanceMethod:
和 resolveClassMethod:
動態(tài)實現(xiàn)一個實例和類的selector。
一個OC方法其實是一個至少帶有self(接受對象)和 _cmd(執(zhí)行的selector)的 C語言函數(shù).我們可以通過class_addMethod:
將一個函數(shù)添加成一個類中的方法,而添加的過程可以在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ā)消息和動態(tài)決議是緊密相關(guān)的。一個類在消息轉(zhuǎn)發(fā)機制(forwarding mechanism)結(jié)束之前有一個機會動態(tài)處理一個方法,調(diào)用respondsToSelector:
和instancesRespondToSelector:
會先為selector提供IMP,從而可以進行動態(tài)方法處理。
如果你實現(xiàn)了resolveInstanceMethod:這個方法,但是想讓某些selector通過消息轉(zhuǎn)發(fā)機制轉(zhuǎn)發(fā),在實現(xiàn)中判斷如果是這些selector 就return NO;
2、動態(tài)加載(Dynamic Loading)
OC程序可以在運行時加載和鏈接新的類,新加入的代碼會合并進程序中,與一開始加載的類或類別同等對待。
動態(tài)加載可以用來做很多事情。例如,App的系統(tǒng)設(shè)置便是動態(tài)加載。
在Cocoa環(huán)境中,常用于App功能的自定義定制。你的程序可以在運行時加載其他模塊,例如 Interface Builder加載顏色,和OS X系統(tǒng)設(shè)置應(yīng)用加載偏好設(shè)置模塊。動態(tài)加載模塊擴展了應(yīng)用的功能。
You provide the framework, but others provide the code.
Runtime 提供了動態(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)一個對象不能正常及時處理發(fā)送過來的消息時會導(dǎo)致異常,runtime系統(tǒng)在發(fā)生異常之前提供了第二次機會處理消息的機會。
1、消息轉(zhuǎn)發(fā)(Forwarding)
如果一個對象沒有正常處理發(fā)送過來的消息,在異常之前 Runtime 向?qū)ο蟀l(fā)送帶有 NSInvocation對象作為基礎(chǔ)參數(shù)的forwardInvocation: 消息,NSinvoction對象收納了初始消息和參數(shù)。
我們可以實現(xiàn)forwardInvocation:
方法做為對消息的默認回應(yīng),防止發(fā)生異常。forwardInvocation:
正如其名字所形容的,就是轉(zhuǎn)發(fā)消息到其他對象。
假如你想設(shè)計一個能響應(yīng)negotiate
方法的對象a,而negotiate
方法的實現(xiàn)是在其他對象b中。你可以通過發(fā)送negotiate
消息到實現(xiàn)這個方法的類b中去,簡單地完成這個功能。
更進一步,假設(shè)你想要這個對象a能精確地(exactly)執(zhí)行negotiate
方法,一個方法是繼承。但是它不可能在兩個類的對象中傳值。
我們可以通過實現(xiàn)一個方法簡單地傳送消息到類b的實例中去“借”negotiate
方法。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
用這個方法會帶來一些麻煩,對于每一個你想"借"的方法,你需要實現(xiàn)一個方法去獲得,而且對于一些未知的方法,你無法去處理。
通過forwardInvocation:
方法可以解決這個問題,這里我們可以采用動態(tài)的方式而不是靜態(tài)。主要工作過程為:當(dāng)一個對象因為沒有匹配的selector方法而不能響應(yīng)一個消息時,runtime系統(tǒng)向這個對象發(fā)送forwardInvocation:
消息,每一個對象都從NSObject繼承了forwardInvocation:
方法,但是在NSObject中,這個方法結(jié)束后直接調(diào)用了doesNotRecognizeSelector:
。我們必須自己重寫forwardInvocation:
方法執(zhí)行之后相關(guān)的實現(xiàn)。這樣我們就可以利用forwardInvocation:
方法轉(zhuǎn)發(fā)消息到其他類中去。
我的github這里面的Messaging文件是消息轉(zhuǎn)發(fā)相關(guān)例子,注釋中有進行了說明。
轉(zhuǎn)發(fā)一個消息時,forwardInvocation:
方法中需要做到:
1、確定消息發(fā)送到何處。
2、發(fā)送時帶上原始參數(shù)。
消息可以通過invokeWithTarget:
方法發(fā)送
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
返回值類型可以被傳回到原始的傳送者(sender)中,包括 id類型,結(jié)構(gòu)體,雙精度浮點數(shù)。
可以把forwardInvocation:
方法當(dāng)做無法辨認的消息的分發(fā)中心,將消息分配給各個接收者,它可以把所有消息發(fā)送到同一個目的地,也可以聯(lián)合幾個消息做出同一個響應(yīng)。forwardInvocation:
方法主要面向方法實現(xiàn),但是它提供的通過消息轉(zhuǎn)發(fā)鏈鏈接對象的機會為程序設(shè)計帶來更多可能性。
Note:
forwardInvocation:
只有在調(diào)用了不存在的方法導(dǎo)致消息無法處理時才會被調(diào)用。
可以到Foundation框架中查看NSInvocation的相關(guān)文檔,獲取更多的詳細內(nèi)容。
2、消息轉(zhuǎn)發(fā)與多繼承(Forwarding and Multiple Inheritance)
消息轉(zhuǎn)發(fā)參照了繼承,在OC程序中可以借用一些多繼承的功能。
在下圖中,一個對象對一個消息做出回應(yīng),類似于借來一個在其他類定義實現(xiàn)的方法。
在這個插圖中,warrior實例轉(zhuǎn)發(fā)了一個negotiate消息到Diplomat實例中,執(zhí)行Diplomat中的negotiate
方法,結(jié)果看起來像是warrior實例執(zhí)行了一個和Diplomat實例一樣的negotiate
方法,其實執(zhí)行者還是Diplomat實例。
在上面的例子中,看起來相當(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ā)提供了許多類似于多繼承的特性,但是他們之間有一個很大的不同:
多繼承:合并了不同的行為特征在一個單獨的對象中,會得到一個重量級多層面的對象。
消息轉(zhuǎn)發(fā):將各個功能分散到不同的對象中,得到的一些輕量級的對象,這些對象通過消息通過消息轉(zhuǎn)發(fā)聯(lián)合起來。
3、代理對象(Surrogate Objects)
消息轉(zhuǎn)發(fā) 不僅參照了多繼承,它還讓用輕量級對象代替重量級對象成為了可能。
通過代理(Surrogate)可以為對象篩選消息。
代理管理發(fā)送到接收者的消息,確定參數(shù)值被復(fù)制,拯救等等。但是它不企圖去做很多其他的,它不重復(fù)對象的功能只是簡單地提供對象一個可以接收來自其他應(yīng)用消息的地址。
舉個例子,有一個重量級對象,里面加入了許多大型數(shù)據(jù),如圖片視頻等,每次使用這個對象的時候都需要讀取磁盤上的內(nèi)容,需要消耗很多時間(time-consuming),所以我們更偏向于采用懶加載模式。
在這樣的情況下,你可以初始化一個簡單的輕量級對象來代理(surrogate)它。利用代理對象可以做到例如查詢數(shù)據(jù)信息等,而不用加載一整個重量級對象。如果是直接用重量級對象的話,它會一直被持有占用資源。當(dāng)代理的forwardInvocation:方法第一次接收消息的時候,它會確保對象是否存在,如果不存在邊創(chuàng)建一個。
當(dāng)這個代理對象發(fā)送的消息覆蓋了這個重量級對象的所有功能時,這個代理對象就相當(dāng)于和重量級對象一樣。
創(chuàng)建一個輕量級的對象來代理一個重量級對象,完成相對應(yīng)的功能,而不用一直持有著重量級對象,從而可以減少資源占用。
4、消息轉(zhuǎn)發(fā)與繼承(Forwarding and Inheritance)
盡管消息轉(zhuǎn)發(fā)參照了繼承,但是NSObject 不會混亂
像 respondsToSelector: 和 isKindOfClass: 這些方法只有在繼承體系里看到,不會出現(xiàn)在消息轉(zhuǎn)發(fā)鏈里。
例如, Warrio 對象是否響應(yīng)negotiate
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
大多情況下答案是No,盡管它能無錯誤地收到negotiate
消息或者某種意義上通過轉(zhuǎn)發(fā)到Diplomat來響應(yīng)。
如果我們想通過消息轉(zhuǎn)發(fā)設(shè)立一個代理對象或擴展類的功能,消息轉(zhuǎn)發(fā)體質(zhì)就得像繼承一樣清晰顯然。如果我們想讓對象看起來真正地繼承了父類對象的行為特征,我們需要去重寫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:
兩個方法外,instancesRespondToSelector
方法也映射著消息轉(zhuǎn)發(fā)規(guī)則。當(dāng)涉及到協(xié)議時我們需要還要考慮conformsToProtocol:
。同樣,一個對象轉(zhuǎn)發(fā)消息時,將會執(zhí)行methodSignatureForSelector:
,它描述了方法的簽名認證等相關(guān)信息,如果一個對象能夠轉(zhuǎn)發(fā)詳細到它的代理,我們需要實現(xiàn)methodSignatureForSelector:
方法。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
注意:這是一個高級用法,只適用于沒有其他可能解決方案的情況下使用,不建議用它來代替繼承。當(dāng)你使用這個方法的時候必須保證你完全熟悉類相關(guān)的行為特征以及消息轉(zhuǎn)發(fā)的情況。
里面涉及的一些方法可以到NSObject 和NSInvocation中去查閱更詳細的內(nèi)容
六、Type Encodings
為了協(xié)助runtime 系統(tǒng),編譯器為每個方法用字符串 編碼 返回和參數(shù) 類型,并將字符串與方法選擇器相關(guān)聯(lián)。所使用的編碼表同樣在其他context里是有用的,所以它是公共可用的with@encode()
編譯程序指令。提供一個類型說明時,@encode()
返回一個編碼這個類型的字符串。可以是基礎(chǔ)類型,int, 指針,結(jié)構(gòu)體,聯(lián)合體。或一個類,事實上,可以用來做C語言sizeof()
操作的參數(shù)
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
下面是類型編碼表。里面有許多是與archive 和 distribution 編碼時重復(fù)的編碼,但是
可以到NSCoder查閱更詳細的內(nèi)容
重要:OC不支持long double 類型,@encode(long double)將會返回d,即double類型;
數(shù)組的類型編碼是在方括號里面的包含一個代表元素個數(shù)的數(shù)字和一個數(shù)組元素的類型編碼,例如一個包含12個浮點數(shù)的數(shù)組:
[12^f]
結(jié)構(gòu)體類型編碼的顯示是一個大括號里面包含名稱和變量的類型編碼,例如:
typedef struct example {
id anObject;
char *aString;
int anInt;
} Example;
這個結(jié)構(gòu)體的類型編碼是:
{example=@*i}
指向這個結(jié)構(gòu)體的結(jié)構(gòu)體指針類型編碼為:
^{example=@*i}
還有另外一種去除了內(nèi)部的說明:
^^{example}
對象(Object)與結(jié)構(gòu)體類似,例如NSObject的編碼:
{NSObject=#}
NSObject只聲明一個實例變量:isa,它是一個Class。
該注意的是,盡管@encode()指令不返回這些編碼,但是當(dāng)他們在協(xié)議中聲明的方法中被使用到時,runtime系統(tǒng)為類型限定符提供了另外的編碼,如下圖。
七、聲明屬性(Declared Properties)
編譯器在屬性聲明(Property declarations)的時候,它生成與類、類別、協(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)
例如:有這么一個類如下:
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
可以獲得其屬性隊列:
id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
獲取屬性名稱:
const char *property_getName(objc_property_t property)
可以通過一個已知名字的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類型編碼,接著是逗號,接著是V,接著是屬性名,在這中間,使用下面這個表中的符號,用逗號隔開。
具體例子看下面的屬性特性例子。
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; };
下面是屬性類型編程字符串的例子:
常見使用:
runtime 常見的使用有:
動態(tài)交換兩個方法的實現(xiàn)
實現(xiàn)分類也可以添加屬性
實現(xiàn)NSCoding的自動歸檔和解檔
實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換
Hook
這里是代碼完整版WXSRuntime
動態(tài)交換兩個方法的實現(xiàn)
NSLog(@"------Normal-----\\n");
ShowExchange *normarlTest = [ShowExchange new];
[normarlTest firstMethod];
NSLog(@"------Normal-----\\n");
//交換實例方法
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");
實現(xiàn)分類也可以添加屬性
-(void)setWxsTitle:(NSString *)wxsTitle {
objc_setAssociatedObject(self, WXSAddPropertyKeyTitle, wxsTitle, OBJC_ASSOCIATION_RETAIN);
}
-(NSString *)wxsTitle {
return objc_getAssociatedObject(self, WXSAddPropertyKeyTitle);
}
實現(xiàn)NSCoding的自動歸檔和解檔
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);
實現(xiàn)字典轉(zhuǎn)模型的自動轉(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ù)組時
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ù)為字典時
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)在的實現(xiàn)為交換方法m2的實現(xiàn),現(xiàn)在將源方法m1的實現(xiàn)替換到交換方法m2中
class_replaceMethod([self class], @selector(wxs_viewWillAppear:), method_getImplementation(m1), method_getTypeEncoding(m1));
}else {
//添加失敗:說明源方法已經(jīng)有實現(xiàn),直接將兩個方法的實現(xiàn)交換即
method_exchangeImplementations(m1, m2);
}
}
-(void)viewWillAppear:(BOOL)animated {
NSLog(@"viewWillAppear");
}
- (void)wxs_viewWillAppear:(BOOL)animated {
NSLog(@"Hook : 攔截到viewwillApear的實現(xiàn),在其基礎(chǔ)上添加了這行代碼");
[self wxs_viewWillAppear:YES];
}
結(jié)語
原本準(zhǔn)備一個星期完成這篇文章,但是很多東西都盡量想面面俱到,導(dǎo)致戰(zhàn)線太長,用了接近半個月的時間,一些名詞,一些用法都必須努力地保證正確性,但是肯定會有不對的地方,茫茫人海中看到這邊文章的都是有緣人,歡迎指出不恰當(dāng)?shù)牡胤剑蠹覝贤ń涣鳎餐M步。
為這篇文章寫的Demo:WXSRuntime 歡迎大家star ,download
這篇文章參考引用了:
官方文檔
標(biāo)哥的技術(shù)博客