前言
Runtime是iOS開發(fā)者進(jìn)階必須學(xué)習(xí)的一個(gè)知識(shí)點(diǎn)抑进。網(wǎng)上關(guān)于Runtime 有許多介紹,有深入有簡(jiǎn)單介紹婿禽,也有實(shí)際應(yīng)用舉例赏僧,但是都不夠系統(tǒng),相關(guān)的知識(shí)點(diǎn)得不到關(guān)聯(lián)扭倾,對(duì)runtime 的認(rèn)知不能形成一個(gè)體系淀零。這里參照蘋果官方文檔,加上自己的一些理解膛壹,進(jìn)行了系統(tǒng)的介紹總結(jié)驾中。
目錄
一、 Runtime版本與平臺(tái)介紹
二模聋、 使用Runtime的場(chǎng)景
- Objective-C Source Code
- NSObject Methods
- Runtime Functions
三肩民、消息機(jī)制(Messaging)
- objc_msgSend 函數(shù)
- 使用隱藏的參數(shù)
- 獲得方法地址
四、動(dòng)態(tài)方法決議
- 動(dòng)態(tài)方法決議(Dynamic Method Resolution)
- 動(dòng)態(tài)加載(Dynamic Loading)
五链方、消息轉(zhuǎn)發(fā)
- 消息轉(zhuǎn)發(fā)(Forwarding)
- 消息轉(zhuǎn)發(fā)與多繼承(Forwarding and Multiple Inheritance)
- 代理對(duì)象(Surrogate Objects)
- 消息轉(zhuǎn)發(fā)與繼承(Forwarding and Inheritance)
六持痰、Type Encodings
七、聲明屬性(Declared Properties)
- 屬性類型和函數(shù)(Property Type and Functions)
- 屬性類型字符串(Property Type String)
- 屬性特性(Property Attribute)例子
正文
一祟蚀、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í)悬槽,編譯器通過(guò)Runtime的消息機(jī)制在幕后完成創(chuàng)建數(shù)據(jù)、調(diào)用函數(shù)瞬浓。Runtime的實(shí)質(zhì)是消息的發(fā)送初婆,官方文檔稱之為Messaging,翻譯成中文為消息機(jī)制猿棉。消息機(jī)制在OC源碼使用過(guò)程中會(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: 用來(lái)判斷是否是某個(gè)類或其子類的實(shí)例isMemberOfClass: 用來(lái)判斷是否是某個(gè)類的實(shí)例 respondsToSelector: 用來(lái)判斷是否有以某個(gè)名字命名的方法(被封裝在一個(gè)selector的對(duì)象里傳遞)instancesRespondToSelector: 用來(lái)判斷實(shí)例是否有以某個(gè)名字命名的方法conformsToProtocol: 判斷是否遵循相關(guān)協(xié)議 methodForSelector: 可以獲得一個(gè)指向方法實(shí)現(xiàn)的指針弊琴,并可以使用該指針直接調(diào)用方法實(shí)現(xiàn)
3. Runtime Functions
Runtime系統(tǒng)是一個(gè)C語(yǔ)言動(dòng)態(tài)庫(kù),在Xcode的/usr/include/objc里可以看到由一些列函數(shù)和數(shù)據(jù)結(jié)構(gòu)構(gòu)成的公共接口杖爽,里面的許多函數(shù)可以用C語(yǔ)言去調(diào)用敲董。這些函數(shù)可以在開發(fā)環(huán)境中用來(lái)生成一些工具紫皇,在一些功能場(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萄窜、通過(guò)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)苇侵,通過(guò)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指針陡鹃。
消息發(fā)送到一個(gè)對(duì)象時(shí)烘浦,通過(guò)class結(jié)構(gòu)體里的isa指針在dispatch table里尋找相應(yīng)的 selector,如果找不到便進(jìn)入其父類里找,一直到NSObject. 一旦定位到selector 萍鲸,便調(diào)用該方法闷叉,傳遞相關(guān)數(shù)據(jù)。為了提高效率脊阴,Runtime 會(huì)緩存調(diào)用過(guò)的selector 和方法地址握侧,在到disaptch table查找之前,先到Catch里查找嘿期。
2. 使用隱藏的參數(shù)
在objc_msgSend運(yùn)作過(guò)程中品擎,在傳送消息中傳遞所有參數(shù),包括兩個(gè)隱藏起來(lái)的參數(shù): 1备徐、接收對(duì)象 2萄传、方法的selector 這兩個(gè)參數(shù)為每個(gè)方法的實(shí)現(xiàn)提供調(diào)用時(shí)的相關(guān)信息,之所以說(shuō)是被隱藏是因?yàn)樵诖a中不用聲明這兩個(gè)參數(shù)蜜猾,沒(méi)有體現(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. 獲得方法地址
繞過(guò)動(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ù)類型念赶,為了以防萬(wàn)一础钠,返回和參數(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ù)在方法語(yǔ)句中被隱藏了旗吁,但是當(dāng)方法作為函數(shù)被調(diào)用時(shí)必須明確存在。 使用methodForSelector:方法繞過(guò)動(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語(yǔ)言自身的特性。
四工禾、動(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)。 我們可以通過(guò)resolveInstanceMethod: 和 resolveClassMethod: 動(dòng)態(tài)實(shí)現(xiàn)一個(gè)實(shí)例和類的selector癣丧。 一個(gè)OC方法其實(shí)是一個(gè)至少帶有self(接受對(duì)象)和 _cmd(執(zhí)行的selector)的 C語(yǔ)言函數(shù).我們可以通過(guò)class_addMethod:將一個(gè)函數(shù)添加成一個(gè)類中的方法槽畔,而添加的過(guò)程可以在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通過(guò)消息轉(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)加載可以用來(lá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ā)送過(guò)來(lái)的消息時(shí)會(huì)導(dǎo)致異常,runtime系統(tǒng)在發(fā)生異常之前提供了第二次機(jī)會(huì)處理消息的機(jī)會(huì)硼啤。1.消息轉(zhuǎn)發(fā)(Forwarding)
如果一個(gè)對(duì)象沒(méi)有正常處理發(fā)送過(guò)來(lái)的消息议经,在異常之前 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中渠抹。你可以通過(guò)發(fā)送negotiate消息到實(shí)現(xiàn)這個(gè)方法的類b中去蝙昙,簡(jiǎn)單地完成這個(gè)功能。 更進(jìn)一步梧却,假設(shè)你想要這個(gè)對(duì)象a能精確地(exactly)執(zhí)行negotiate方法奇颠,一個(gè)方法是繼承。但是它不可能在兩個(gè)類的對(duì)象中傳值放航。 我們可以通過(guò)實(shí)現(xiàn)一個(gè)方法簡(jiǎn)單地傳送消息到類b的實(shí)例中去“借”negotiate方法烈拒。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
用這個(gè)方法會(huì)帶來(lái)一些麻煩,對(duì)于每一個(gè)你想"借"的方法,你需要實(shí)現(xiàn)一個(gè)方法去獲得荆几,而且對(duì)于一些未知的方法吓妆,你無(wú)法去處理。 通過(guò)forwardInvocation:
方法可以解決這個(gè)問(wèn)題吨铸,這里我們可以采用動(dòng)態(tài)的方式而不是靜態(tài)行拢。主要工作過(guò)程為:當(dāng)一個(gè)對(duì)象因?yàn)闆](méi)有匹配的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)行了說(shuō)明握巢。 轉(zhuǎn)發(fā)一個(gè)消息時(shí)晕鹊,forwardInvocation:
方法中需要做到: 1、確定消息發(fā)送到何處暴浦。 2溅话、發(fā)送時(shí)帶上原始參數(shù)。
消息可以通過(guò)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)做無(wú)法辨認(rèn)的消息的分發(fā)中心屑墨,將消息分配給各個(gè)接收者,它可以把所有消息發(fā)送到同一個(gè)目的地纷铣,也可以聯(lián)合幾個(gè)消息做出同一個(gè)響應(yīng)卵史。forwardInvocation:方法主要面向方法實(shí)現(xiàn),但是它提供的通過(guò)消息轉(zhuǎn)發(fā)鏈鏈接對(duì)象的機(jī)會(huì)為程序設(shè)計(jì)帶來(lái)更多可能性搜立。 Note: forwardInvocation: 只有在調(diào)用了不存在的方法導(dǎo)致消息無(wú)法處理時(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)址晕,類似于借來(lái)一個(gè)在其他類定義實(shí)現(xiàn)的方法。
在這個(gè)插圖中蒜哀,warrior實(shí)例轉(zhuǎn)發(fā)了一個(gè)negotiate消息到Diplomat實(shí)例中斩箫,執(zhí)行Diplomat中的negotiate方法,結(jié)果看起來(lái)像是warrior實(shí)例執(zhí)行了一個(gè)和Diplomat實(shí)例一樣的negotiate方法撵儿,其實(shí)執(zhí)行者還是Diplomat實(shí)例乘客。 在上面的例子中,看起來(lái)相當(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ì)象通過(guò)消息通過(guò)消息轉(zhuǎn)發(fā)聯(lián)合起來(lái)纳决。
3. 代理對(duì)象(Surrogate Objects)
消息轉(zhuǎn)發(fā) 不僅參照了多繼承碰逸,它還讓用輕量級(jí)對(duì)象代替重量級(jí)對(duì)象成為了可能。 通過(guò)代理(Surrogate)可以為對(duì)象篩選消息阔加。 代理管理發(fā)送到接收者的消息,確定參數(shù)值被復(fù)制饵史,拯救等等。但是它不企圖去做很多其他的胜榔,它不重復(fù)對(duì)象的功能只是簡(jiǎn)單地提供對(duì)象一個(gè)可以接收來(lái)自其他應(yīng)用消息的地址胳喷。 舉個(gè)例子,有一個(gè)重量級(jí)對(duì)象夭织,里面加入了許多大型數(shù)據(jù)吭露,如圖片視頻等,每次使用這個(gè)對(duì)象的時(shí)候都需要讀取磁盤上的內(nèi)容尊惰,需要消耗很多時(shí)間(time-consuming)讲竿,所以我們更偏向于采用懶加載模式。 在這樣的情況下弄屡,你可以初始化一個(gè)簡(jiǎn)單的輕量級(jí)對(duì)象來(lái)代理(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ì)象來(lái)代理一個(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,盡管它能無(wú)錯(cuò)誤地收到negotiate消息或者某種意義上通過(guò)轉(zhuǎn)發(fā)到Diplomat來(lái)響應(yīng)铝阐。 如果我們想通過(guò)消息轉(zhuǎn)發(fā)設(shè)立一個(gè)代理對(duì)象或擴(kuò)展類的功能址貌,消息轉(zhuǎn)發(fā)體質(zhì)就得像繼承一樣清晰顯然。如果我們想讓對(duì)象看起來(lái)真正地繼承了父類對(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í)用法,只適用于沒(méi)有其他可能解決方案的情況下使用政恍,不建議用它來(lái)代替繼承汪拥。當(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è)類型說(shuō)明時(shí),@encode()返回一個(gè)編碼這個(gè)類型的字符串蒙保≡铮可以是基礎(chǔ)類型,int, 指針邓厕,結(jié)構(gòu)體逝嚎,聯(lián)合體∠昴眨或一個(gè)類补君,事實(shí)上,可以用來(lái)做C語(yǔ)言sizeof()操作的參數(shù)
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
下面是類型編碼表昧互。里面有許多是與archive 和 distribution 編碼時(shí)重復(fù)的編碼挽铁,但是 可以到NSCoder查閱更詳細(xì)的內(nèi)容
重要: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)部的說(shuō)明:
^^{example}
對(duì)象(Object)與結(jié)構(gòu)體類似,例如NSObject的編碼:
{NSObject=#}
NSObject只聲明一個(gè)實(shí)例變量:isa,它是一個(gè)Class够掠。
該注意的是民褂,盡管@encode()指令不返回這些編碼茄菊,
但是當(dāng)他們?cè)趨f(xié)議中聲明的方法中被使用到時(shí)疯潭,runtime系統(tǒng)為類型限定符提供了另外的編碼,如下圖
七面殖、聲明屬性(Declared Properties)
編譯器在屬性聲明(Property declarations)的時(shí)候竖哩,它生成與類、類別脊僚、協(xié)議相關(guān)聯(lián)的元數(shù)據(jù)相叁,我們可以通過(guò)這些元數(shù)據(jù)使用這些函數(shù):通過(guò)名字查看屬性、得到屬性的類型(@encode串形式)辽幌、復(fù)制出屬性的相關(guān)參數(shù)(C語(yǔ)言字符串形式)列表等增淹。
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)
可以通過(guò)一個(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));
}
3. 屬性類型字符串(Property Type String)
可以用property_getAttributes函數(shù)得到屬性的名字加酵、類型編碼字符串拳喻、以及特性。 字符串以T開頭猪腕,后面接著@encode類型編碼冗澈,接著是逗號(hào),接著是V,接著是屬性名陋葡,在這中間亚亲,使用下面這個(gè)表中的符號(hào),用逗號(hào)隔開腐缤。
具體例子看下面的屬性特性例子朵栖。
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 常見的使用有: 動(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)
{
// 添加成功:說(shuō)明源方法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
{
//添加失敳癜稹:說(shuō)明源方法已經(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];
}
結(jié)語(yǔ)
茫茫人海中看到這邊文章的都是有緣人绍在,歡迎指出不恰當(dāng)?shù)牡胤矫派龋蠹覝贤ń涣鞅⒂校餐M(jìn)步
官方文檔