專題:運行時

一步绸、什么是運行時(Runtime)?

運行時是蘋果提供的純C語言的開發(fā)庫(運行時是一種非常牛逼、開發(fā)中經(jīng)常用到的底層技術(shù))

二歹茶、運行時的作用捎拯?

能獲得某個類的所有成員變量

能獲得某個類的所有屬性

能獲得某個類的所有方法

交換方法實現(xiàn)

能動態(tài)添加一個成員變量

能動態(tài)添加一個屬性

能動態(tài)添加一個方法

三、案例:運行時獲取成員變量名稱

1毯欣、分析

#import #import"XMGPerson.h"#import intmain(intargc,constchar* argv[]){? ? @autoreleasepool {// 成員變量的數(shù)量unsignedintoutCount =0;// 獲得所有的成員變量// ivars是一個指向成員變量的指針// ivars默認指向第0個成員變量(最前面)Ivar *ivars = class_copyIvarList([XMGPersonclass], &outCount);// 遍歷所有的成員變量for(inti =0; i

2馒过、獲取UITextFiled成員變量的名稱

Snip20151027_1.png

// 成員變量的數(shù)量unsignedintoutCount =0;// 獲得所有的成員變量Ivar *ivars = class_copyIvarList([UITextFieldclass], &outCount);// 遍歷所有的成員變量for(inti =0; i

四、iOS底層

1仪媒、The Runtime 簡單介紹

Objective-C是一門簡單的語言沉桌,95%是C。只是在語言層面上加了些關(guān)鍵字和語法算吩。真正讓Objective-C如此強大的是它的運行時留凭。它很小但卻很強大。它的核心是消息分發(fā)偎巢。

Messages

執(zhí)行一個方法蔼夜,有些語言,編譯器會執(zhí)行一些額外的優(yōu)化和錯誤檢查压昼,因為調(diào)用關(guān)系很直接也很明顯求冷。但對于消息分發(fā)來說,就不那么明顯了窍霞。在發(fā)消息前不必知道某個對象是否能夠處理消息匠题。你把消息發(fā)給它,它可能會處理但金,也可能轉(zhuǎn)給其他的Object來處理韭山。一個消息不必對應(yīng)一個方法,一個對象可能實現(xiàn)一個方法來處理多條消息冷溃。

在Objective-C中钱磅,消息是通過objc_msgSend()這個runtime方法及相近的方法來實現(xiàn)的。這個方法需要一個target似枕,selector盖淡,還有一些參數(shù)。理論上來說凿歼,編譯器只是把消息分發(fā)變成objc_msgSend來執(zhí)行褪迟。比如下面這兩行代碼是等價的。

[arrayinsertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo,5);

Objects, Classes, MetaClasses

大多數(shù)面向?qū)ο蟮恼Z言里有 classes 和 objects 的概念毅往。Objects通過Classes生成牵咙。但是在Objective-C中,classes本身也是objects攀唯,也可以處理消息洁桌,這也是為什么會有類方法和實例方法。具體來說侯嘀,Objective-C中的Object是一個結(jié)構(gòu)體(struct)另凌,第一個成員是isa谱轨,指向自己的class。這是在objc/objc.h中定義的吠谢。

typedefstructobjc_object {? ? Class isa;} *id;

object的class保存了方法列表土童,還有指向父類的指針。但classes也是objects工坊,也會有isa變量献汗,那么它又指向哪兒呢?這里就引出了第三個類型: metaclasses王污。一個 metaclass被指向class罢吃,class被指向object。它保存了所有實現(xiàn)的方法列表昭齐,以及父類的metaclass尿招。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以閱讀這篇文章阱驾。

Methods, Selectors and IMPs

我們知道了運行時會發(fā)消息給對象就谜。我們也知道一個對象的class保存了方法列表。那么這些消息是如何映射到方法的里覆,這些方法又是如何被執(zhí)行的呢丧荐?

第一個問題的答案很簡單。class的方法列表其實是一個字典喧枷,key為selectors篮奄,IMPs為value。一個IMP是指向方法在內(nèi)存中的實現(xiàn)割去。很重要的一點是,selector和IMP之間的關(guān)系是在運行時才決定的昼丑,而不是編譯時呻逆。這樣我們就能玩出些花樣。

IMP通常是指向方法的指針菩帝,第一個參數(shù)是self咖城,類型為id,第二個參數(shù)是_cmd呼奢,類型為SEL宜雀,余下的是方法的參數(shù)。這也是self和_cmd被定義的地方握础。下面演示了Method和IMP

- (id)doSomethingWithInt:(int)aInt{}iddoSomethingWithInt(idself, SEL _cmd,intaInt){}

現(xiàn)在我們知道了objects,classes,selectors,IMPs以及消息分發(fā)辐董,那么運行時到底能做什么呢?

運行時到底能做什么呢禀综?

作用:

創(chuàng)建简烘、修改苔严、自省classes和objects

消息分發(fā)

之前已經(jīng)提過消息分發(fā),不過這只是一小部分功能孤澎。所有的運行時方法都有特定的前綴届氢。下面是一些有意思的方法:

class

class開頭的方法是用來修改和自省classes。

方法如:

能拿到一個class的所有內(nèi)容

class_addIvar, class_addMethod, class_addProperty和class_addProtocol允許重建classes覆旭。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList

返回單個內(nèi)容

class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty

一些通用的自省方法

class_conformsToProtocol, class_respondsToSelector, class_getSuperclass

創(chuàng)建一個object

class_createInstance來創(chuàng)建一個object

ivar

這些方法能讓你得到名字退子,內(nèi)存地址和Objective-C type encoding。

method

這些方法主要用來自省型将,比如:

method_getName, method_getImplementation, method_getReturnType等等

也有一些修改的方法寂祥,包括:

method_setImplementation和method_exchangeImplementations

objc

一旦拿到了object,你就可以對它做一些自省和修改茶敏。你可以get/set ivar, 使用object_copy和object_dispose來copy和free object的內(nèi)存壤靶。不僅是拿到一個class,而是可以使用object_setClass來改變一個object的class惊搏。

property

屬性保存了很大一部分信息贮乳。除了拿到名字,你還可以使用property_getAttributes來發(fā)現(xiàn)property的更多信息恬惯,如返回值向拆、是否為atomic、getter/setter名字酪耳、是否為dynamic浓恳、背后使用的ivar名字、是否為弱引用碗暗。

protocol

Protocols有點像classes颈将,但是精簡版的,運行時的方法是一樣的言疗。你可以獲取method, property, protocol列表, 檢查是否實現(xiàn)了其他的protocol晴圾。

sel

最后我們有一些方法可以處理 selectors,比如獲取名字噪奄,注冊一個selector等等死姚。

2、運行時能干什么勤篮?(舉例)

2.1 Classes And Selectors From Strings

比較基礎(chǔ)的一個動態(tài)特性是通過String來生成Classes和Selectors都毒。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起來很簡單:

Class stringclass =NSClassFromString(@"NSString")

于是我們就得到了一個string class碰缔。接下來:

NSString*myString = [stringclass stringWithString:@"Hello World"];

為什么要這么做呢账劲?直接使用Class不是更方便?通常情況下是,但有些場景下這個方法會很有用涤垫。首先姑尺,可以得知是否存在某個class,NSClassFromString 會返回nil蝠猬,如果運行時不存在該class的話切蟋。

另一個使用場景是根據(jù)不同的輸入返回不同的class或method。比如你在解析一些數(shù)據(jù)榆芦,每個數(shù)據(jù)項都有要解析的字符串以及自身的類型(String柄粹,Number,Array)匆绣。你可以在一個方法里搞定這些驻右,也可以使用多個方法。其中一個方法是獲取type崎淳,然后使用if來調(diào)用匹配的方法堪夭。另一種是根據(jù)type來生成一個selector,然后調(diào)用之拣凹。以下是兩種實現(xiàn)方式:

- (void)parseObject:(id)object {for(iddatainobject) {if([[data type] isEqualToString:@"String"]) {? ? ? ? ? ? [selfparseString:[data value]];? ? ? ? }elseif([[data type] isEqualToString:@"Number"]) {? ? ? ? ? ? [selfparseNumber:[data value]];? ? ? ? }elseif([[data type] isEqualToString:@"Array"]) {? ? ? ? ? ? [selfparseArray:[data value]];? ? ? ? }? ? }}- (void)parseObjectDynamic:(id)object {for(iddatainobject) {? ? ? ? [selfperformSelector:NSSelectorFromString([NSStringstringWithFormat:@"parse%@:", [data type]]) withObject:[data value]];? ? }}- (void)parseString:(NSString*)aString {}- (void)parseNumber:(NSString*)aNumber {}- (void)parseArray:(NSString*)aArray {}

可一看到森爽,你可以把7行帶if的代碼變成1行。將來如果有新的類型嚣镜,只需增加實現(xiàn)方法即可爬迟,而不用再去添加新的 else if。

2.2 Method Swizzling

之前我們講過菊匿,方法由兩個部分組成付呕。Selector相當(dāng)于一個方法的id;IMP是方法的實現(xiàn)跌捆。這樣分開的一個便利之處是selector和IMP之間的對應(yīng)關(guān)系可以被改變徽职。比如一個 IMP 可以有多個 selectors 指向它。

而 Method Swizzling 可以交換兩個方法的實現(xiàn)佩厚』罨或許你會問“什么情況下會需要這個呢?”可款。我們先來看下Objective-C中,兩種擴展class的途徑克蚂。首先是 subclassing闺鲸。你可以重寫某個方法,調(diào)用父類的實現(xiàn)埃叭,這也意味著你必須使用這個subclass的實例摸恍,但如果繼承了某個Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。這種情況下立镶,你會想添加一個方法到NSArray壁袄,也就是使用Category。99%的情況下這是OK的媚媒,但如果你重寫了某個方法嗜逻,就沒有機會再調(diào)用原先的實現(xiàn)了。

Method Swizzling 可以搞定這個問題缭召。你可以重寫某個方法而不用繼承栈顷,同時還可以調(diào)用原先的實現(xiàn)。通常的做法是在category中添加一個方法(當(dāng)然也可以是一個全新的class)嵌巷√逊铮可以通過method_exchangeImplementations這個運行時方法來交換實現(xiàn)。來看一個demo搪哪,這個demo演示了如何重寫addObject:方法來紀(jì)錄每一個新添加的對象靡努。

#import@interfaceNSMutableArray(LoggingAddObject)- (void)logAddObject:(id)aObject;@end@implementationNSMutableArray(LoggingAddObject)+ (void)load {? ? Method addobject = class_getInstanceMethod(self,@selector(addObject:));? ? Method logAddobject = class_getInstanceMethod(self,@selector(logAddObject:));? ? method_exchangeImplementations(addObject, logAddObject);}- (void)logAddObject:(id)aobject {? ? [selflogAddObject:aObject];NSLog(@"Added object %@ to array %@", aObject,self);}@end

我們把方法交換放到了load中,這個方法只會被調(diào)用一次晓折,而且是運行時載入惑朦。如果指向臨時用一下,可以放到別的地方已维。注意到一個很明顯的遞歸調(diào)用logAddObject:行嗤。這也是Method Swizzling容易把我們搞混的地方,因為我們已經(jīng)交換了方法的實現(xiàn)垛耳,所以其實調(diào)用的是addObject:

動態(tài)繼承栅屏、交換

我們可以在運行時創(chuàng)建新的class,這個特性用得不多堂鲜,但其實它還是很強大的栈雳。你能通過它創(chuàng)建新的子類,并添加新的方法缔莲。

但這樣的一個子類有什么用呢哥纫?別忘了Objective-C的一個關(guān)鍵點:object內(nèi)部有一個叫做isa的變量指向它的class。這個變量可以被改變痴奏,而不需要重新創(chuàng)建蛀骇。然后就可以添加新的ivar和方法了《敛穑可以通過以下命令來修改一個object的class.

object_setClass(myObject, [MySubclass class]);

這可以用在Key Value Observing擅憔。當(dāng)你開始o(jì)bserving an object時,Cocoa會創(chuàng)建這個object的class的subclass檐晕,然后將這個object的isa指向新創(chuàng)建的subclass暑诸。

動態(tài)方法處理

目前為止蚌讼,我們討論了方法交換,以及已有方法的處理个榕。那么當(dāng)你發(fā)送了一個object無法處理的消息時會發(fā)生什么呢篡石?很明顯,"it breaks"西采。大多數(shù)情況下確實如此凰萨,但Cocoa和runtime也提供了一些應(yīng)對方法。

首先是動態(tài)方法處理苛让。通常來說沟蔑,處理一個方法,運行時尋找匹配的selector然后執(zhí)行之狱杰。有時瘦材,你只想在運行時才創(chuàng)建某個方法,比如有些信息只有在運行時才能得到仿畸。要實現(xiàn)這個效果食棕,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果確實增加了一個方法错沽,記得返回YES簿晓。

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {if(aSelector ==@selector(myDynamicMethod)) {? ? ? ? class_addMethod(self, aSelector, (IMP)myDynamicIMP,"v@:");returnYES;? ? }return[superresolveInstanceMethod:aSelector];}

那Cocoa在什么場景下會使用這些方法呢?Core Data用得很多千埃。NSManagedObjects有許多在運行時添加的屬性用來處理get/set屬性和關(guān)系憔儿。那如果Model在運行時被改變了呢?

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

如果 resolve method 返回NO放可,運行時就進入下一步驟:消息轉(zhuǎn)發(fā)谒臼。有兩種常見用例。1) 將消息轉(zhuǎn)發(fā)到另一個可以處理該消息的object耀里。2) 將多個消息轉(zhuǎn)發(fā)到同一個方法蜈缤。

消息轉(zhuǎn)發(fā)分兩步。首先冯挎,運行時調(diào)用-forwardingTargetForSelector:底哥,如果只是想把消息發(fā)送到另一個object,那么就使用這個方法房官,因為更高效趾徽。如果想要修改消息,那么就要使用-forwardInvocation:翰守,運行時將消息打包成NSInvocation孵奶,然后返回給你處理。處理完之后潦俺,調(diào)用invokeWithTarget:。

Cocoa有幾處地方用到了消息轉(zhuǎn)發(fā),主要的兩個地方是代理(Proxies)和響應(yīng)鏈(Responder Chain)事示。NSProxy是一個輕量級的class早像,它的作用就是轉(zhuǎn)發(fā)消息到另一個object。如果想要惰性加載object的某個屬性會很有用肖爵。NSUndoManager也有用到卢鹦,不過是截取消息,之后再執(zhí)行劝堪,而不是轉(zhuǎn)發(fā)到其他的地方冀自。

響應(yīng)鏈?zhǔn)顷P(guān)于Cocoa如何處理與發(fā)送事件與行為到對應(yīng)的對象。比如說秒啦,使用Cmd+C執(zhí)行了copy命令熬粗,會發(fā)送-copy:到響應(yīng)鏈。首先是First Responder余境,通常是當(dāng)前的UI驻呐。如果沒有處理該消息,則轉(zhuǎn)發(fā)到下一個-nextResponder芳来。這么一直下去直到找到能夠處理該消息的object含末,或者沒有找到,報錯即舌。

使用Block作為Method IMP

iOS 4.3帶來了很多新的runtime方法佣盒。除了對properties和protocols的加強,還帶來一組新的以 imp 開頭的方法顽聂。通常一個 IMP 是一個指向方法實現(xiàn)的指針肥惭,頭兩個參數(shù)為 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 帶來了block芜飘,imp_implementationWithBlock() 能讓我們使用block作為 IMP务豺,下面這個代碼片段展示了如何使用block來添加新的方法。

IMP myIMP = imp_implementationWithBlock(^(id_self,NSString*string) {NSLog(@"Hello %@", string);});class_addMethod([MYclass class],@selector(sayHello:), myIMP,"v@:@");

可以看到嗦明,Objective-C 表面看起來挺簡單笼沥,但還是很靈活的,可以帶來很多可能性娶牌。動態(tài)語言的優(yōu)勢在于在不擴展語言本身的情況下做很多很靈巧的事情奔浅。比如Key Value Observing,提供了優(yōu)雅的API可以與已有的代碼無縫結(jié)合诗良,而不需要新增語言級別的特性汹桦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鉴裹,隨后出現(xiàn)的幾起案子舞骆,更是在濱河造成了極大的恐慌钥弯,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件督禽,死亡現(xiàn)場離奇詭異脆霎,居然都是意外死亡,警方通過查閱死者的電腦和手機狈惫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門睛蛛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胧谈,你說我怎么就攤上這事忆肾。” “怎么了菱肖?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵客冈,是天一觀的道長。 經(jīng)常有香客問我蔑滓,道長郊酒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任键袱,我火速辦了婚禮燎窘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹄咖。我一直安慰自己褐健,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布澜汤。 她就那樣靜靜地躺著蚜迅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俊抵。 梳的紋絲不亂的頭發(fā)上谁不,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天埃碱,我揣著相機與錄音锅纺,去河邊找鬼。 笑死票堵,一個胖子當(dāng)著我的面吹牛谎替,可吹牛的內(nèi)容都是我干的偷溺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼钱贯,長吁一口氣:“原來是場噩夢啊……” “哼挫掏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秩命,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤尉共,失蹤者是張志新(化名)和其女友劉穎褒傅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體袄友,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡樊卓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了杠河。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浇辜,死狀恐怖券敌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柳洋,我是刑警寧澤待诅,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站熊镣,受9級特大地震影響卑雁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绪囱,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一测蹲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鬼吵,春花似錦扣甲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涣脚,卻和暖如春示辈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遣蚀。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工矾麻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人妙同。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓射富,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粥帚。 傳聞我的和親對象是個殘疾皇子胰耗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

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

  • Objective-C語言是一門動態(tài)語言,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運行時來處理芒涡。這種動態(tài)語言...
    tigger丨閱讀 1,382評論 0 8
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉柴灯,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,681評論 0 9
  • 本文轉(zhuǎn)載自:http://southpeak.github.io/2014/10/25/objective-c-r...
    idiot_lin閱讀 924評論 0 4
  • 原文出處:南峰子的技術(shù)博客 Objective-C語言是一門動態(tài)語言卖漫,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了...
    _燴面_閱讀 1,215評論 1 5
  • 一、什么是運行時(Runtime)? 運行時是蘋果提供的純C語言的開發(fā)庫(運行時是一種非常牛逼赠群、開發(fā)中經(jīng)常用到的底...
    iOS_成才錄閱讀 10,673評論 14 66