一步绸、什么是運行時(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é)合诗良,而不需要新增語言級別的特性汹桦。