runtime官方文檔翻譯

本文只是單純的翻譯幽污,如果您感覺(jué)枯燥可以參考我這篇比較實(shí)用的文章 文章地址吁津,結(jié)合demo我相信您很快會(huì)熟悉runtime機(jī)制更胖。

OC是一種面向?qū)ο蟮膭?dòng)態(tài)語(yǔ)言署尤,作為初學(xué)者可能大多數(shù)人對(duì)面向?qū)ο筮@個(gè)概念理解的比較深检盼,而對(duì)OC是動(dòng)態(tài)語(yǔ)言這一特性了解的比較少洲脂。那么什么是動(dòng)態(tài)語(yǔ)言赎瑰?動(dòng)態(tài)語(yǔ)言就是在運(yùn)行時(shí)來(lái)執(zhí)行靜態(tài)語(yǔ)言的編譯鏈接的工作深寥。這就要求除了編譯器之外還要有一種運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行編譯等功能潮酒。OC中這個(gè)系統(tǒng)就是runtime义屏。

OC的runtime是用C語(yǔ)言和編譯語(yǔ)言編寫的一個(gè)runtime庫(kù)舞箍,它使C語(yǔ)言有了面向?qū)ο蟮奶匦浴?/p>

版本

OC中的運(yùn)行時(shí)分為兩個(gè)版本——Modern Runtime和Legacy Runtime〗⑼剩現(xiàn)在的運(yùn)行時(shí)與以前的運(yùn)行時(shí)區(qū)別在于:以前的運(yùn)行時(shí)在改變一個(gè)類的結(jié)構(gòu)時(shí),你必須繼承它并重新編譯疏橄。而現(xiàn)在的運(yùn)行時(shí)可以直接編譯占拍。

iPhone應(yīng)用程序和64程序在OX v10.5和以后使用現(xiàn)在版本的運(yùn)行時(shí)。其他項(xiàng)目的使用的都是以前版本的運(yùn)行時(shí)捎迫。

OC程序與運(yùn)行時(shí)系統(tǒng)交互分為三個(gè)不同等級(jí):通過(guò)OC源代碼晃酒;通過(guò)定義在Foudation框架中NSObject中的方法;通過(guò)直接調(diào)用運(yùn)行時(shí)的函數(shù)窄绒。

通過(guò)OC源代碼

在大多數(shù)情況下贝次,運(yùn)行時(shí)會(huì)自動(dòng)在幕后工作。你使用它只是編寫和編譯OC源代碼彰导。

當(dāng)你編譯的代碼包含OC中的類和方法時(shí)蛔翅,編譯器創(chuàng)建數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用,實(shí)現(xiàn)語(yǔ)言的動(dòng)態(tài)特性位谋。數(shù)據(jù)結(jié)構(gòu)捕獲類山析,分類和協(xié)議中聲明的信息。其中包括在OC中討論類和協(xié)議對(duì)象的定義掏父,以及從源代碼中提取出來(lái)方法選擇器笋轨,實(shí)例模板和其他信息。運(yùn)行時(shí)的主要功能就是傳遞消息赊淑,正如消息傳遞中所描述的那樣爵政。它通過(guò)源代碼消息表達(dá)式來(lái)來(lái)調(diào)用。

通過(guò)NSObject中定義的方法

在Cocoa中陶缺,大多數(shù)對(duì)象是NSObject類的子類對(duì)象茂卦,所以大多數(shù)對(duì)象繼承了他定義的方法(NSProxy類除外)。因此它的方法建立每個(gè)實(shí)例组哩,每個(gè)類對(duì)象的行為等龙。然而在少數(shù)情況下,NSOject只定義了一個(gè)怎樣去做的模板伶贰,它本身不提供所有必要的代碼(抽象類蛛砰?)

例如,NSObject定義了一個(gè)返回一個(gè)描述類內(nèi)容的字符串的實(shí)例方法黍衙。這主要用于調(diào)試GDB對(duì)象打印命令從這各類中打印的字符串泥畅。NSObject的方法實(shí)現(xiàn)中不知道類中包含什么內(nèi)容,所以它返回一個(gè)包含對(duì)象名和地址的字符串琅翻。NSObject的子類可以實(shí)現(xiàn)這個(gè)方法返回更多的細(xì)節(jié)位仁。例如柑贞,F(xiàn)oundation中NSSArray返回一個(gè)它包含對(duì)象的描述列表。

NSObject方法的一些簡(jiǎn)單的查詢的運(yùn)行時(shí)系統(tǒng)信息聂抢。這些方法允許對(duì)象自示弧(自我查找)。這種方法的例子是類方法琳疏,例如isKindOfClass:問(wèn)一個(gè)對(duì)象來(lái)確定它的類:isMemberOfClass測(cè)試對(duì)象在繼承結(jié)構(gòu)中的層次位置有决,respondsToSelector,這表明一個(gè)對(duì)象是否能接受特定的消息,conformsToProtocol:確定對(duì)象是否實(shí)現(xiàn)在特定協(xié)議中定義的方法空盼,methodForSelector:提供方法實(shí)現(xiàn)的地址书幕。像這樣的方法給予了對(duì)象自省的能力。

直接調(diào)用運(yùn)行時(shí)的函數(shù)

運(yùn)行時(shí)系統(tǒng)是一個(gè)定義在/usr/include/objc目錄下的揽趾,有一個(gè)公共接口在它頭文件中包含一系列方法和數(shù)據(jù)結(jié)構(gòu)動(dòng)態(tài)共享庫(kù)台汇。這里面許多方法允許你使用C語(yǔ)言重復(fù)編譯器在你寫OC代碼時(shí)是怎樣工作的。其他基礎(chǔ)功能形式通過(guò)NSObject類的方法來(lái)導(dǎo)出篱瞎。當(dāng)OC中不需要時(shí)苟呐,這些方法使開(kāi)發(fā)runtime的其他接口,生產(chǎn)出增強(qiáng)開(kāi)發(fā)環(huán)境的工具成為可能奔缠。然而掠抬,一小些運(yùn)行時(shí)函數(shù)只能在編寫OC程序時(shí)有用。所有的功能都記錄在Objective-C Runtime Reference.中校哎。

消息傳遞機(jī)制

這一部分描述了如何把消息表達(dá)式轉(zhuǎn)換成objc_msgSend函數(shù)調(diào)用两波,怎樣通過(guò)名字找到方法。然后解釋了如果你需要的話怎么通過(guò)objc_msgSend來(lái)繞過(guò)動(dòng)態(tài)綁定闷哆。

在OC中腰奋,消息不跟方法實(shí)現(xiàn)綁定直到運(yùn)行時(shí)。編譯器將消息表達(dá)式 [receiver message] 轉(zhuǎn)化成一個(gè)消息傳遞函數(shù)objc_msgSend抱怔。這個(gè)函數(shù)將接收者和在消息中提到的方法名(方法選擇器)作為他的兩個(gè)主要參數(shù):objc_msgSend(receiver, selector)劣坊。消息中任何參數(shù)也交給objc_msgSend:objc_msgSend(receiver, selector, arg1, arg2, ...)。

消息傳遞函數(shù)為動(dòng)態(tài)綁定做了所有必須的事情:

它首先發(fā)現(xiàn)方法選擇器指向的程序(方法的實(shí)現(xiàn))屈留。因?yàn)橄嗤姆椒梢员徊煌念惙謩e實(shí)現(xiàn)局冰。這個(gè)準(zhǔn)確的程序依賴于接收者的類。

然后調(diào)用程序灌危,通過(guò)接收對(duì)象(指針指向他的數(shù)據(jù))為方法傳遞指定的參數(shù)康二。

最后,當(dāng)他返回值的時(shí)候它傳遞程序的返回值勇蝙。

提示:編譯器對(duì)消息傳遞函數(shù)生成調(diào)用沫勿,在你的代碼中不要直接調(diào)用。

消息傳遞機(jī)制的關(guān)鍵在于編譯器對(duì)每個(gè)類和對(duì)象的結(jié)構(gòu)的構(gòu)建,每個(gè)類結(jié)構(gòu)包含兩個(gè)基本元素:指向父類的指針和類調(diào)度表产雹。這個(gè)表羅列了他們定義的有明確類特征的方法的地址的方法選擇器诫惭。例如,setOrigin::方法的選擇器與setOrigin::方法的實(shí)現(xiàn)聯(lián)系起來(lái)蔓挖,展示方法的選擇器關(guān)聯(lián)展示的地址等等夕土。

創(chuàng)建新對(duì)象時(shí),分配內(nèi)存时甚,實(shí)例變量被初始化隘弊。首先在對(duì)象中有一個(gè)指向它的類結(jié)構(gòu)的指針變量哈踱。這個(gè)指針被稱為isa指針荒适,它使對(duì)象能夠訪問(wèn)類,通過(guò)類可以訪問(wèn)它繼承的所有的類开镣。

注意:雖然不是嚴(yán)格意義上語(yǔ)言的一部分刀诬,isa指針需要一個(gè)對(duì)象運(yùn)行在OC運(yùn)行時(shí)系統(tǒng)。一個(gè)對(duì)象需要等效的objc_object結(jié)構(gòu)體無(wú)論是定義在這個(gè)結(jié)構(gòu)的任意字段邪财。然而陕壹,你很少甚至從來(lái)不需要?jiǎng)?chuàng)建你自己的根對(duì)象,繼承自NSObject 或者 NSProxy的對(duì)象自動(dòng)擁有可變的isa指針树埠。

這些類的元素和結(jié)構(gòu)如下圖:


類的元素和結(jié)構(gòu)

當(dāng)一個(gè)消息傳遞給一個(gè)對(duì)象的時(shí)候糠馆,消息函數(shù)沿著這個(gè)對(duì)象的isa指針在調(diào)度表找到它建立起方法選擇器的類結(jié)構(gòu)。如果它不能在這里發(fā)現(xiàn)選擇器怎憋,obic_msgSend根據(jù)指針找到它的父類又碌,在父類的調(diào)度表中尋找選擇器。連續(xù)失敗導(dǎo)致objc_msgSend沿著類繼承結(jié)構(gòu)直到尋找到NSObject類绊袋。一旦確定選擇器的位置毕匀,函數(shù)調(diào)用表中的方法并且把它傳給接收對(duì)象的數(shù)據(jù)結(jié)構(gòu)。

這就是運(yùn)行時(shí)方法選擇實(shí)現(xiàn)的選擇方法癌别,在面向?qū)ο蟮木幊绦g(shù)語(yǔ)中我們可以說(shuō)方法和消息是動(dòng)態(tài)綁定的皂岔。

為了加速消息傳遞過(guò)程,在方法被使用時(shí)展姐,運(yùn)行時(shí)系統(tǒng)緩存了方法的選擇器和地址躁垛。每個(gè)類都有一個(gè)單獨(dú)的緩存,它包含了繼承的方法和自己類中定義的方法的選擇器圾笨。在查找調(diào)度表之前教馆,消息例行程序首先會(huì)在接收者對(duì)象的類的緩存中查找。(理論上來(lái)說(shuō)墅拭,用過(guò)一次的方法很可能再次被使用)如果方法選擇器在緩存里面活玲,消息傳遞只會(huì)比函數(shù)調(diào)用慢一點(diǎn)。如果一個(gè)程序運(yùn)行的足夠長(zhǎng)的事件來(lái)“熱身”緩存,幾乎所有的他發(fā)送的消息可以找到一個(gè)緩存的方法舒憾。當(dāng)程序運(yùn)行時(shí)镀钓,緩存根據(jù)新發(fā)送的消息動(dòng)態(tài)增長(zhǎng)。

使用隱藏參數(shù)

當(dāng)objc_msgSend找到一個(gè)方法的實(shí)現(xiàn)程序镀迂,它調(diào)用這個(gè)程序丁溅,傳遞消息中的所有參數(shù)。它也傳遞給程序兩個(gè)隱藏參數(shù):接收對(duì)象和方法選擇器

這些參數(shù)給了每個(gè)方法實(shí)現(xiàn)關(guān)于調(diào)用它的兩部分消息表達(dá)的明確信息探遵,它們被說(shuō)成隱藏的是因?yàn)樗鼈冊(cè)诙x方法的源代碼中沒(méi)有聲明窟赏。當(dāng)代碼被編譯的時(shí)候它們被插入實(shí)現(xiàn)中。

雖然這些參數(shù)沒(méi)有被顯式聲明箱季,源代碼仍然可以引用他們(就像它可以接收實(shí)例變量一樣)一個(gè)方法引用接收對(duì)象作為自己涯穷,引用他自己的方法選擇器作為_(kāi)cmd。在下面的實(shí)例中藏雏,_cmd引用strange方法的選擇器拷况,自己作為strange消息的接收對(duì)象。


Self比兩個(gè)參數(shù)更有用掘殴。事實(shí)上赚瘦,這是接收對(duì)象的實(shí)例變量提供了方法的定義方式。

獲取方法地址

為了避免動(dòng)態(tài)綁定的唯一方法是得到一個(gè)方法的地址奏寨,當(dāng)他是函數(shù)的時(shí)候直接調(diào)用起意。這可能是極少數(shù)的情況下是合適的,當(dāng)一個(gè)特定的方法陸續(xù)執(zhí)行了很多次病瞳,你想節(jié)省每次方法調(diào)用時(shí)的開(kāi)銷揽咕。

一個(gè)定義在NSObject中的方法,methodForSelector:,你可以要求一個(gè)指針指向它仍源,然后通過(guò)指針來(lái)調(diào)用他心褐。methodForSelector:這個(gè)指針必須返回正確的函數(shù)類型。同時(shí)返回值和參數(shù)的類型也應(yīng)該包含在內(nèi)笼踩。

下面的例子展示實(shí)現(xiàn)setFilled:方法的程序可能是如何被調(diào)用的:


setFilled

首先兩個(gè)參數(shù)傳遞給接收對(duì)象是self方法選擇器是_cmd的程序逗爹。這些參數(shù)被隱藏在方法的語(yǔ)法中但是在這個(gè)方法作為一個(gè)函數(shù)調(diào)用的時(shí)候必須明確。

使用methodForSelector:規(guī)避動(dòng)態(tài)綁定可以節(jié)省大多數(shù)信息傳遞的時(shí)間嚎于。然而掘而,只有當(dāng)一個(gè)特定的方法執(zhí)行很多次的時(shí)候節(jié)省的消耗才比較明顯,就像上面for循環(huán)所示于购。

注意:methodForSelector:是運(yùn)行時(shí)系統(tǒng)提供的而不是OC的特點(diǎn)袍睡。

動(dòng)態(tài)方法解析

這一章講述了你可以動(dòng)態(tài)的提供一個(gè)方法的實(shí)現(xiàn)

有某種情況下,你可能需要?jiǎng)討B(tài)地為你的方法提供實(shí)現(xiàn)肋僧。比如斑胜,這個(gè)OC聲明屬性中包含@dynamic指令的時(shí)候:

@dynamic propertyName;

它告訴編譯器與屬性相關(guān)聯(lián)的方法將動(dòng)態(tài)提供控淡。

你可以實(shí)現(xiàn)方法resolveinstancemethod:和resolveclassmethod:分別為實(shí)例和類方法提供一個(gè)選擇器。

OC方法是一個(gè)至少包含self和_cmd兩個(gè)參數(shù)的C函數(shù)止潘。當(dāng)一個(gè)方法使用class_addMethod函數(shù)的時(shí)候可以為一個(gè)類添加函數(shù)掺炭。因此,給了以下函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {

    // implementation ....

}

你也可以把它作為一個(gè)方法添加到一個(gè)類中(調(diào)用resolveThisMethodDynamically)就像這樣:

@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

方法轉(zhuǎn)發(fā)和動(dòng)態(tài)方法解析在很大程度上是有關(guān)系的凭戴。一個(gè)類可以在消息轉(zhuǎn)發(fā)機(jī)制起作用前動(dòng)態(tài)提供一個(gè)方法涧狮。如果respondstoselector:或instancesrespondtoselector:被調(diào)用時(shí),動(dòng)態(tài)方法解析器首先有機(jī)會(huì)為選擇器提供IMP么夫。如果你只不過(guò)是實(shí)現(xiàn)了resolveInstanceMethod:想要通過(guò)轉(zhuǎn)發(fā)機(jī)制轉(zhuǎn)發(fā)特別的選擇器者冤,你應(yīng)該為那些選擇器返回NO;

動(dòng)態(tài)加載

一個(gè)OC在它運(yùn)行的時(shí)候可以加載鏈接很多類和分類档痪。加入的新代碼和一開(kāi)始加載的類和分類做相同處理涉枫。

動(dòng)態(tài)加載可以用來(lái)做很多不同的事情。比如在系統(tǒng)偏好設(shè)置的各個(gè)模塊中動(dòng)態(tài)加載钞它。

在Cocoa中拜银,動(dòng)態(tài)加載經(jīng)常被用于程序定制殊鞭。別人修改寫你在運(yùn)行時(shí)加載的程序遭垛,比如說(shuō)當(dāng)界面生成器加載自定義調(diào)色板和OS X系統(tǒng)偏好設(shè)置自定義模塊加載應(yīng)用程序的偏好的時(shí)候。加載模塊擴(kuò)展你的應(yīng)用程序操灿。他們有助于你允許但沒(méi)有預(yù)計(jì)或者定義锯仪。你可以提供框架別人提供代碼。

即使runtime函數(shù)提供了在Objective-C Mach-O文件動(dòng)態(tài)加載模塊趾盐,然而Cocoa的NSBundle類提供了一個(gè)面向?qū)ο蟮膭?dòng)態(tài)加載和相關(guān)服務(wù)集成更方便的接口庶喜。可以在Foudation框架引用中查找NSBulde的詳細(xì)說(shuō)明和它如何讓使用救鲤。

消息轉(zhuǎn)發(fā)

如果你給一個(gè)不處理這個(gè)消息對(duì)象發(fā)送消息久窟,在認(rèn)識(shí)到時(shí)一個(gè)錯(cuò)誤之前運(yùn)行時(shí)會(huì)給對(duì)象發(fā)送一個(gè)帶有NSInvocation對(duì)象作為唯一參數(shù)的forwardInvocation:消息。這個(gè)NSInvocation封裝了原始的消息本缠,參數(shù)通過(guò)它傳遞斥扛。

你可以通過(guò)實(shí)現(xiàn)forwardInvocation:方法來(lái)指定一個(gè)默認(rèn)的響應(yīng)或者通過(guò)其他方式來(lái)避免這個(gè)錯(cuò)誤。正如它的名字按時(shí)的那樣丹锹,forwardInvocation:通常用于抓發(fā)消息給另一個(gè)對(duì)象稀颁。

要查看轉(zhuǎn)發(fā)的范圍和意圖,你可以想象以下情況:首先楣黍,你假設(shè)你正在設(shè)計(jì)一個(gè)可以響應(yīng)談判消息的對(duì)象匾灶,并且他可以響應(yīng)另外一種對(duì)象的響應(yīng)。你可以輕易地通過(guò)發(fā)消息給另外一個(gè)包含你實(shí)現(xiàn)談判方法的對(duì)象來(lái)實(shí)現(xiàn)租漂。

進(jìn)一步說(shuō)阶女,你想你的對(duì)象對(duì)于談判消息的精確的在另外一個(gè)類中響應(yīng)颊糜。實(shí)現(xiàn)這一方法的方式是讓你的類繼承于別的類的方法。然而秃踩,它不可能通過(guò)這種方式來(lái)安排事情芭析。這有很多好的為什么你的類和實(shí)現(xiàn)了談判的類在繼承結(jié)構(gòu)的不同分支的原因。

即使你的類不能繼承談判方法吞瞪,你也可以通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單傳遞給另一個(gè)類的實(shí)例消息的方法中的一個(gè)版本來(lái)“借用”它:

- (id)negotiate
{

    if ( [someOtherObject respondsTo:@selector(negotiate)] )

        return [someOtherObject negotiate];

    return self;

}

這種方式可能有點(diǎn)麻煩馁启,特別是當(dāng)你希望你的對(duì)象傳遞一些消息給另外一個(gè)對(duì)象的時(shí)候。你不得不實(shí)現(xiàn)每個(gè)你想從其他類中借用的方法芍秆。然而惯疙,在你寫代碼的時(shí)候你不可能處理你不知道所有你想要轉(zhuǎn)發(fā)的消息的集合的情況。這個(gè)集合可能依賴于運(yùn)行時(shí)中的事件妖啥,也可能在將來(lái)新實(shí)現(xiàn)類和新方法的時(shí)候改變霉颠。
forwardInvocation:消息提供了第二個(gè)機(jī)會(huì):另外一個(gè)不是那么特別的解決方案,是動(dòng)態(tài)而不是靜態(tài)荆虱。它是像這樣工作的:當(dāng)一個(gè)對(duì)象因?yàn)闆](méi)有這個(gè)消息對(duì)應(yīng)的方法選擇器來(lái)響應(yīng)這個(gè)消息蒿偎。運(yùn)行時(shí)系統(tǒng)通過(guò)發(fā)forwardInvocation:消息通知對(duì)象。每個(gè)對(duì)象都從NSObject類中繼承了一個(gè)forwardInvocation:方法怀读。然而诉位,NSObjcet類中的方法版本只是僅僅調(diào)用了doesNotRecognizeSelector:。通過(guò)重寫NSObject類實(shí)現(xiàn)的你自己的版本菜枷,forwardInvocation:消息提供想另一個(gè)對(duì)象轉(zhuǎn)發(fā)消息的時(shí)候抓住這個(gè)機(jī)會(huì)苍糠。
forwardInvocation:轉(zhuǎn)發(fā)消息時(shí)所有該做的事情是:1.確定消息要傳到哪2.帶著原始參數(shù)把它發(fā)送過(guò)去。
消息會(huì)隨著invokeWithTarget:方法發(fā)送:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

轉(zhuǎn)發(fā)消息的返回值返回給原始發(fā)送者啤誊。所有類型的返回值都可以傳遞給發(fā)送者岳瞭,包括id類型,結(jié)構(gòu)體蚊锹,單精度和雙精度浮點(diǎn)數(shù)瞳筏。
forwardInvocation:像一個(gè)為無(wú)法識(shí)別消息工作的分配中心,把他們打包到不同的接收器牡昆。也可以作為一個(gè)中轉(zhuǎn)站姚炕,把所有信息發(fā)送到一個(gè)目的地。他可以轉(zhuǎn)運(yùn)一些消息到其他地方迁杨,也可以“吞食”一些方法钻心,所以這里沒(méi)有響應(yīng)和錯(cuò)誤。forwardInvocation:也可以把幾條消息合并到一個(gè)響應(yīng)中铅协。forwardInvocation:做的是把上交給實(shí)現(xiàn)者捷沸。然而,它為在轉(zhuǎn)發(fā)鏈上上的連接對(duì)象打開(kāi)了程序設(shè)計(jì)的可能狐史。
注意:forwardInvocation:方法只能處理那些名義上沒(méi)有存在調(diào)用方法的消息痒给。例如说墨,你想要你的對(duì)象轉(zhuǎn)發(fā)談判消息給另外一個(gè)對(duì)象,它不能有自己的談判方法苍柏。如果有尼斧,消息永遠(yuǎn)不會(huì)到達(dá)nominal receiver。

轉(zhuǎn)發(fā)和多繼承

轉(zhuǎn)發(fā)模擬繼承试吁,可為OC程序提供多繼承效果棺棵,如下圖所示,一個(gè)對(duì)象響應(yīng)一個(gè)消息可以通過(guò)借用或者繼承其他類的方法實(shí)現(xiàn)

在本示例中,戰(zhàn)士類的一個(gè)實(shí)例將談判消息轉(zhuǎn)發(fā)到外交官類的一個(gè)實(shí)例熄捍。談判的戰(zhàn)士將會(huì)出現(xiàn)像一個(gè)外交官烛恤。似乎將應(yīng)對(duì)談判信息,,實(shí)際上它回應(yīng)(盡管它真的是一個(gè)外交官做的工作)

轉(zhuǎn)發(fā)消息的對(duì)象因此“繼承”來(lái)自兩個(gè)繼承層次結(jié)構(gòu)的方法,一個(gè)是自己的分支余耽,另一個(gè)是響應(yīng)這個(gè)消息的對(duì)象缚柏。在上面的示例中,這看起來(lái)就像是戰(zhàn)士類繼承自外交官以及自己的超類。
轉(zhuǎn)發(fā)提供了大多數(shù)你想從多繼承活的功能碟贾。然而币喧,兩者之間最大的區(qū)別在于:多繼承是結(jié)合不同的功能在一個(gè)對(duì)象中。它傾向于大的袱耽,多方面的對(duì)象杀餐。另一方面,轉(zhuǎn)發(fā)機(jī)制將不同的功能分配給不同的對(duì)象扛邑。它把大的問(wèn)題分解成小的對(duì)象怜浅,但是通過(guò)對(duì)消息發(fā)送者透明來(lái)把這些對(duì)象關(guān)聯(lián)起來(lái)。

代理對(duì)象

轉(zhuǎn)發(fā)不僅模仿多繼承蔬崩,它也使開(kāi)發(fā)輕量級(jí)的代表或者“覆蓋”更大量的對(duì)象的對(duì)象。代理就代表了其他的對(duì)象搀暑,篩選傳遞給他的消息沥阳。
在OC編程語(yǔ)言中的遠(yuǎn)程通信中是這樣一個(gè)代理。代理需要照顧轉(zhuǎn)發(fā)到遠(yuǎn)程接收者的消息的管理細(xì)節(jié)自点,確保通過(guò)連接的參數(shù)值被復(fù)制和檢索等等桐罕。但它并沒(méi)有嘗試去做其他的事情;它不復(fù)制遠(yuǎn)程對(duì)象的功能桂敛,只是給給遠(yuǎn)程對(duì)象一個(gè)本地但它并沒(méi)有嘗試去做其他的事情功炮;它不復(fù)制遠(yuǎn)程對(duì)象的功能,但只要給遠(yuǎn)程對(duì)象一個(gè)可以在另一個(gè)應(yīng)用程序中接收消息的本地地址术唬。
其他類型的代理對(duì)象也可能薪伏。例如,假設(shè)你有一個(gè)對(duì)象粗仓,操縱大量數(shù)據(jù)嫁怀,也許它創(chuàng)建了一個(gè)復(fù)雜的圖像或讀取磁盤上的文件的內(nèi)容设捐。設(shè)置這個(gè)對(duì)象是費(fèi)時(shí)的,所以你喜歡懶加載它塘淑,當(dāng)它真正需要的時(shí)候或當(dāng)系統(tǒng)資源暫時(shí)閑置的時(shí)候萝招。同時(shí),你需要至少一個(gè)占位符對(duì)象存捺,其他對(duì)象在應(yīng)用程序正常運(yùn)行槐沼。
在這種情況下,你可以創(chuàng)建一個(gè)輕量級(jí)的不完整的對(duì)象替代他捌治。這個(gè)對(duì)象可以做到一些相對(duì)的事情母赵,比如說(shuō)回答關(guān)于數(shù)據(jù)的問(wèn)題,但是大多數(shù)情況下具滴,它僅僅為一個(gè)大對(duì)象占位置凹嘲,當(dāng)時(shí)間到了,轉(zhuǎn)發(fā)消息給它构韵。這個(gè)代理的forwardInvocation:方法第一次接收到目的地為另一個(gè)對(duì)象的消息周蹭,他會(huì)確定這個(gè)對(duì)象是否存在,如果不存在就創(chuàng)建它疲恢。所有的大對(duì)象的消息都是通過(guò)代理凶朗,就程序的其他部分來(lái)說(shuō),代理和大對(duì)象是一樣的显拳。

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

雖然轉(zhuǎn)發(fā)模擬繼承棚愤,但是NSObject類從來(lái)不會(huì)混淆兩者。像respondsToSelector: 和isKindOfClass:這樣的方法只查看結(jié)構(gòu)杂数,從來(lái)不在轉(zhuǎn)發(fā)鏈上宛畦。例如,如果一個(gè)戰(zhàn)士對(duì)象被問(wèn)到它是否會(huì)對(duì)談判信息作出反應(yīng):
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
答案是不會(huì)揍移,即使在某種意義上它可以通過(guò)轉(zhuǎn)發(fā)給一個(gè)外交官?zèng)]有錯(cuò)誤地接收談判消息次和,并響應(yīng)它,
在大多數(shù)情況下那伐,不是正確答案踏施。但也有可能不是。如果你使用轉(zhuǎn)發(fā)來(lái)設(shè)置代理對(duì)象或者擴(kuò)展一個(gè)類的功能罕邀,轉(zhuǎn)發(fā)機(jī)制可能是像繼承一樣透明畅形。如果你想你的對(duì)象像他們真正繼承他們轉(zhuǎn)發(fā)消息的對(duì)象的行為一樣,你需要在respondsToSelector: 和isKindOfClass:中重新實(shí)現(xiàn)你的轉(zhuǎn)發(fā)算法诉探。

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ( [super respondsToSelector:aSelector] )
        return YES; 
    else {
         
    } 
    return NO; 
}

除了respondsToSelector: 和isKindOfClass:方法之外日熬,instancesRespondToSelector:方法中也應(yīng)該復(fù)制轉(zhuǎn)發(fā)算法。如果使用協(xié)議阵具,conformstoprotocol:方法也應(yīng)該被添加到列表中碍遍。同樣定铜,如果一個(gè)對(duì)象轉(zhuǎn)發(fā)任何它接收到的遠(yuǎn)程消息,它應(yīng)該有一個(gè)可以返回最終響應(yīng)轉(zhuǎn)發(fā)消息的methodsignatureforselector:的該寫版怕敬。例如揣炕,如果一個(gè)對(duì)象能夠?qū)⑾⑥D(zhuǎn)發(fā)給它的代理,你會(huì)實(shí)現(xiàn)methodsignatureforselector:如下:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector 
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];

    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
 
    return signature;
}

你可能會(huì)考慮將轉(zhuǎn)發(fā)算法封裝到某個(gè)地方东跪,讓所有方法包括forwardInvocation:調(diào)用他畸陡。
注意:這是一門先進(jìn)的技術(shù),僅僅是用于沒(méi)有別的解決方案虽填。不是作為繼承的替代品丁恭。如果你必須使用這個(gè)技術(shù),確保你對(duì)轉(zhuǎn)發(fā)消息的類和要轉(zhuǎn)發(fā)的類的行為有充分的了解斋日。

類型編碼

為了幫助運(yùn)行時(shí)系統(tǒng)牲览,編譯器將每個(gè)方法中的返回和參數(shù)類型進(jìn)行編碼,并將該字符串與該方法選擇器關(guān)聯(lián)恶守。在其他情況下第献,編碼體系也是很有用的,所以編碼體系是帶有@encode()編譯指令的工公共的可用的兔港。當(dāng)給一個(gè)指定類型庸毫,@encode()返回指定的類型的字符串編碼。這個(gè)類型可以是任何類型衫樊,可以是基本類型飒赃,如int型指針,可以是一個(gè)標(biāo)記結(jié)構(gòu)或聯(lián)合科侈,或類名载佳,可以被C語(yǔ)言的sizeof()運(yùn)算符作為參數(shù)使用。
下面的表格列出了編碼類型兑徘。注意當(dāng)對(duì)一個(gè)對(duì)象歸檔或者分發(fā)時(shí)刚盈,他們中的許多代碼與你使用的代碼重疊。然而挂脑,這些列表中的編碼在你歸檔的時(shí)候不能使用他們,你可能想要在歸檔使用那些不是@encode()生成的代碼欲侮。

編碼類型

重要提示:OC不支持long double類型崭闲。@encode(long double)返回跟編碼double一樣返回d。
數(shù)組類型的編碼是包括方括號(hào)在內(nèi)威蕉。數(shù)組中的元素?cái)?shù)目在打開(kāi)括號(hào)之后立即指定刁俭,在數(shù)組類型之前。例如韧涨,一個(gè)指向12個(gè)float類型的數(shù)組將被編碼成:
[12^f]
結(jié)構(gòu)體在大括號(hào)內(nèi)定義牍戚,聯(lián)合體在遠(yuǎn)括號(hào)內(nèi)定義侮繁。結(jié)構(gòu)體的標(biāo)簽首先被列出,然后一個(gè)等號(hào)和結(jié)構(gòu)域的編碼順序列出如孝。例如下面這個(gè)結(jié)構(gòu)體:

typedef struct example {
    id   anObject; 
    char *aString; 
    int  anInt;
} Example;

將會(huì)編碼成
{example=@*i}
如果定義類型為(Example)或者(example)經(jīng)過(guò)@encode()將會(huì)得到相同的編碼結(jié)果宪哩。結(jié)構(gòu)指針的編碼攜帶相同數(shù)量的結(jié)構(gòu)域的信息:
^{example=@*i}
然而間接尋址去除了內(nèi)部類型的詳細(xì)描述
對(duì)象被視為結(jié)構(gòu)。例如第晰,通過(guò)NSObject類名稱@ encode()方產(chǎn)生這種編碼:{NSObject=#}
一個(gè)類只聲明一種isa指針變量
注意:當(dāng)他們?cè)趨f(xié)議中聲明方法的時(shí)候锁孟,即使@encode()命令不返回他們,運(yùn)行時(shí)系統(tǒng)使用下表中的補(bǔ)充的編碼茁瘦。

聲明屬性

當(dāng)編譯器遇到屬性聲明品抽,它生成與外圍類,分類和協(xié)議相關(guān)的描述性元數(shù)據(jù)甜熔。你可以使用支持通過(guò)名字查看類圆恤,分類,協(xié)議中的屬性的方法來(lái)查看這個(gè)元數(shù)據(jù)腔稀,獲得這個(gè)屬性的@encode字符串類型盆昙,復(fù)制成一個(gè)C語(yǔ)言字符串?dāng)?shù)組屬性屬性列表。聲明屬性的列表可用于每個(gè)類和協(xié)議烧颖。

屬性類型和方法

屬性結(jié)構(gòu)定義一個(gè)屬性描述符的不透明句柄弱左。

typedef struct objc_property *Property;

你可以使用class_copyPropertyList和protocol_copyPropertyList分別檢索與類,加載分類和協(xié)議相關(guān)的屬性數(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);

你可以使用property_getName函數(shù)發(fā)現(xiàn)屬性的名稱

const char *property_getName(objc_property_t property)

你可以在一個(gè)類或協(xié)議中指定一個(gè)名字炕淮,可以使用class_getProperty和protocol_getProperty分別獲得引用拆火。

objc_property_t class_getProperty(Class cls, const char *name)

objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

你可以使用property_getAttributes這個(gè)函數(shù)去獲得屬性的名字和編碼字符串。了解編碼類型字符串詳情涂圆,看類型編碼们镜,了解字符串詳情,看屬性字符串類型和屬性描述的例子:

const char *property_getAttributes(objc_property_t property)

把這些放在一起润歉,你可以使用下面的代碼打印一個(gè)類的所有屬性的列表:

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));
}

屬性類型字符串

你可以使用property_getAttributes這個(gè)函數(shù)去獲得屬性的名字和編碼字符串模狭,和一些其他屬性。
字符串以T打頭后面跟著編碼類型和逗號(hào)踩衩,結(jié)束是以V打頭加上返回實(shí)例變量的名字嚼鹉。在兩者中間以逗號(hào)隔開(kāi)。
以下是聲明類型屬性編碼

屬性編碼

下面的表展示了相同的屬性聲明和property_getAttributes:返回對(duì)應(yīng)的字符串:

圖1
圖2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驱富,一起剝皮案震驚了整個(gè)濱河市锚赤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌褐鸥,老刑警劉巖线脚,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡浑侥,警方通過(guò)查閱死者的電腦和手機(jī)姊舵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)寓落,“玉大人括丁,你說(shuō)我怎么就攤上這事×闳纾” “怎么了躏将?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)考蕾。 經(jīng)常有香客問(wèn)我祸憋,道長(zhǎng),這世上最難降的妖魔是什么肖卧? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任蚯窥,我火速辦了婚禮,結(jié)果婚禮上塞帐,老公的妹妹穿的比我還像新娘拦赠。我一直安慰自己,他們只是感情好葵姥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布荷鼠。 她就那樣靜靜地躺著,像睡著了一般榔幸。 火紅的嫁衣襯著肌膚如雪允乐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天削咆,我揣著相機(jī)與錄音牍疏,去河邊找鬼。 笑死拨齐,一個(gè)胖子當(dāng)著我的面吹牛鳞陨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞻惋,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厦滤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了歼狼?” 一聲冷哼從身側(cè)響起馁害,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹂匹,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凹蜈,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡限寞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年忍啸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片履植。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡计雌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玫霎,到底是詐尸還是另有隱情凿滤,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布庶近,位于F島的核電站翁脆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鼻种。R本人自食惡果不足惜反番,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叉钥。 院中可真熱鬧罢缸,春花似錦、人聲如沸投队。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敷鸦。三九已至息楔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轧膘,已是汗流浹背钞螟。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谎碍,地道東北人鳞滨。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蟆淀,于是被迫代替她去往敵國(guó)和親拯啦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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