RunTime 入门
原文鏈接:http://www.jianshu.com/p/59992507f875
?
這是一篇淺顯實用 易記 易理解的關于runtime的解讀。
Runtime 中的方法主要以五個單詞開頭——class(類)、object(實例)、method(方法)、propert(屬性)、ivar(成員變量)
它們代表了方法的操作對象,如class開頭的
OBJC_EXPORT Ivar *class_copyIvarList(Class cls, unsigned int *outCount)?
?? ? __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
這個方法很嚇人,有很多單詞,一看腦子就疼,但大部分單詞不是我們關心的,只提取中間一段
class_copyIvarList?? 這是一個方法名,它以class開頭,說明它與class有關(class是類,objc是實例)
而在class(類)里我們可以得到什么呢,沒有被實例化的話好像什么也不是。其實不是這樣的。class是一個模版,屬性、方法、成員變量的信息我們都可以從中獲取。
我們再多分析一點
class_copyIvarList(Class cls, unsigned int *outCount)??這里列出了它需要的參數,一個class類和一個無符號的int類地址。
?Ivar *class_copyIvarList(Class cls, unsigned int *outCount)方法前是它的返回類型,一個ivar類型。ivar類型是class類型成員變量的信息索引,而不是載體。例如我們可以這樣獲得成員變量名
const char* name = ivar_getName(ivar);
可以這樣獲得成員變量指向
id ivarContent = object_getIvar(self, ivar);
可以看到這都不是從ivar中直接取出來的,而是通過ivar用其他辦法獲得的。
?
這里我們又用到兩個不同單詞開頭的方法 ——ivar、object??梢钥吹絠var開頭的方法是用來取變量名字的,而object開頭的方法接受了兩個值——實例和變量索引(ivar類型),它從實例中拿出ivar索引代表的變量取出變量指向做返回值。
?
?
一、什么是運行時(Runtime)?
- 運行時是蘋果提供的純C語言的開發庫(運行時是一種非常牛逼、開發中經常用到的底層技術)
二、運行時的作用?
- 能獲得某個類的所有成員變量
- 能獲得某個類的所有屬性
- 能獲得某個類的所有方法
- 交換方法實現
- 能動態添加一個成員變量
- 能動態添加一個屬性
- 能動態添加一個方法
三、案例:運行時獲取成員變量名稱
- 1、分析
- 2、獲取UITextFiled成員變量的名稱
Snip20151027_1.png // 成員變量的數量unsigned int outCount = 0; // 獲得所有的成員變量 Ivar *ivars = class_copyIvarList([UITextField class], &outCount); // 遍歷所有的成員變量 for (int i = 0; i<outCount; i++) { // 取出i位置對應的成員變量 Ivar ivar = ivars[i]; // 獲得成員變量的名字 NSLog(@"%s", ivar_getName(ivar)); } // 如果函數名中包含了copy\new\retain\create等字眼,那么這個函數返回的數據就需要手動釋放 free(ivars);
四、iOS底層
1、The Runtime 簡單介紹
- Objective-C是一門簡單的語言,95%是C。只是在語言層面上加了些關鍵字和語法。真正讓Objective-C如此強大的是它的運行時。它很小但卻很強大。它的核心是消息分發。
Messages
- 執行一個方法,有些語言,編譯器會執行一些額外的優化和錯誤檢查,因為調用關系很直接也很明顯。但對于消息分發來說,就不那么明顯了。在發消息前不必知道某個對象是否能夠處理消息。你把消息發給它,它可能會處理,也可能轉給其他的Object來處理。一個消息不必對應一個方法,一個對象可能實現一個方法來處理多條消息。
- 在Objective-C中,消息是通過objc_msgSend()這個runtime方法及相近的方法來實現的。這個方法需要一個target,selector,還有一些參數。理論上來說,編譯器只是把消息分發變成objc_msgSend來執行。比如下面這兩行代碼是等價的。
Objects, Classes, MetaClasses
- 大多數面向對象的語言里有 classes 和 objects 的概念。Objects通過Classes生成。但是在Objective-C中,classes本身也是objects,也可以處理消息,這也是為什么會有類方法和實例方法。具體來說,Objective-C中的Object是一個結構體(struct),第一個成員是isa,指向自己的class。這是在objc/objc.h中定義的。
- object的class保存了方法列表,還有指向父類的指針。但classes也是objects,也會有isa變量,那么它又指向哪兒呢?這里就引出了第三個類型: metaclasses。一個 metaclass被指向class,class被指向object。它保存了所有實現的方法列表,以及父類的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以閱讀這篇文章。
Methods, Selectors and IMPs
-
我們知道了運行時會發消息給對象。我們也知道一個對象的class保存了方法列表。那么這些消息是如何映射到方法的,這些方法又是如何被執行的呢?
-
第一個問題的答案很簡單。class的方法列表其實是一個字典,key為selectors,IMPs為value。一個IMP是指向方法在內存中的實現。很重要的一點是,selector和IMP之間的關系是在運行時才決定的,而不是編譯時。這樣我們就能玩出些花樣。
-
IMP通常是指向方法的指針,第一個參數是self,類型為id,第二個參數是_cmd,類型為SEL,余下的是方法的參數。這也是self和_cmd被定義的地方。下面演示了Method和IMP
- 現在我們知道了objects,classes,selectors,IMPs以及消息分發,那么運行時到底能做什么呢?
運行時到底能做什么呢?
-
作用:
- 創建、修改、自省classes和objects
- 消息分發
-
之前已經提過消息分發,不過這只是一小部分功能。所有的運行時方法都有特定的前綴。下面是一些有意思的方法:
class
- class開頭的方法是用來修改和自省classes。
- 方法如:
- 能拿到一個class的所有內容 class_addIvar, class_addMethod, class_addProperty和class_addProtocol允許重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList
- 返回單個內容 class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty
- 一些通用的自省方法 class_conformsToProtocol, class_respondsToSelector, class_getSuperclass
- 創建一個object class_createInstance來創建一個object
ivar
- 這些方法能讓你得到名字,內存地址和Objective-C type encoding。
method
- 這些方法主要用來自省,比如:
- 也有一些修改的方法,包括: method_setImplementation和method_exchangeImplementations
objc
- 一旦拿到了object,你就可以對它做一些自省和修改。你可以get/set ivar, 使用object_copy和object_dispose來copy和free object的內存。不僅是拿到一個class,而是可以使用object_setClass來改變一個object的class。
property
- 屬性保存了很大一部分信息。除了拿到名字,你還可以使用property_getAttributes來發現property的更多信息,如返回值、是否為atomic、getter/setter名字、是否為dynamic、背后使用的ivar名字、是否為弱引用。
protocol
- Protocols有點像classes,但是精簡版的,運行時的方法是一樣的。你可以獲取method, property, protocol列表, 檢查是否實現了其他的protocol。
sel
- 最后我們有一些方法可以處理 selectors,比如獲取名字,注冊一個selector等等。
2、運行時能干什么?(舉例)
2.1 Classes And Selectors From Strings
-
比較基礎的一個動態特性是通過String來生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起來很簡單:
Class stringclass = NSClassFromString(@"NSString") -
于是我們就得到了一個string class。接下來:
NSString *myString = [stringclass stringWithString:@"Hello World"]; -
為什么要這么做呢?直接使用Class不是更方便?通常情況下是,但有些場景下這個方法會很有用。首先,可以得知是否存在某個class,NSClassFromString 會返回nil,如果運行時不存在該class的話。
-
另一個使用場景是根據不同的輸入返回不同的class或method。比如你在解析一些數據,每個數據項都有要解析的字符串以及自身的類型(String,Number,Array)。你可以在一個方法里搞定這些,也可以使用多個方法。其中一個方法是獲取type,然后使用if來調用匹配的方法。另一種是根據type來生成一個selector,然后調用之。以下是兩種實現方式:
- 可一看到,你可以把7行帶if的代碼變成1行。將來如果有新的類型,只需增加實現方法即可,而不用再去添加新的 else if。
2.2 Method Swizzling
-
之前我們講過,方法由兩個部分組成。Selector相當于一個方法的id;IMP是方法的實現。這樣分開的一個便利之處是selector和IMP之間的對應關系可以被改變。比如一個 IMP 可以有多個 selectors 指向它。
-
而 Method Swizzling 可以交換兩個方法的實現?;蛟S你會問“什么情況下會需要這個呢?”。我們先來看下Objective-C中,兩種擴展class的途徑。首先是 subclassing。你可以重寫某個方法,調用父類的實現,這也意味著你必須使用這個subclass的實例,但如果繼承了某個Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。這種情況下,你會想添加一個方法到NSArray,也就是使用Category。99%的情況下這是OK的,但如果你重寫了某個方法,就沒有機會再調用原先的實現了。
-
Method Swizzling 可以搞定這個問題。你可以重寫某個方法而不用繼承,同時還可以調用原先的實現。通常的做法是在category中添加一個方法(當然也可以是一個全新的class)??梢酝ㄟ^method_exchangeImplementations這個運行時方法來交換實現。來看一個demo,這個demo演示了如何重寫addObject:方法來紀錄每一個新添加的對象。
- 我們把方法交換放到了load中,這個方法只會被調用一次,而且是運行時載入。如果指向臨時用一下,可以放到別的地方。注意到一個很明顯的遞歸調用logAddObject:。這也是Method Swizzling容易把我們搞混的地方,因為我們已經交換了方法的實現,所以其實調用的是addObject:
動態繼承、交換
-
我們可以在運行時創建新的class,這個特性用得不多,但其實它還是很強大的。你能通過它創建新的子類,并添加新的方法。
-
但這樣的一個子類有什么用呢?別忘了Objective-C的一個關鍵點:object內部有一個叫做isa的變量指向它的class。這個變量可以被改變,而不需要重新創建。然后就可以添加新的ivar和方法了??梢酝ㄟ^以下命令來修改一個object的class.
object_setClass(myObject, [MySubclass class]); -
這可以用在Key Value Observing。當你開始observing an object時,Cocoa會創建這個object的class的subclass,然后將這個object的isa指向新創建的subclass。
動態方法處理
-
目前為止,我們討論了方法交換,以及已有方法的處理。那么當你發送了一個object無法處理的消息時會發生什么呢?很明顯,"it breaks"。大多數情況下確實如此,但Cocoa和runtime也提供了一些應對方法。
-
首先是動態方法處理。通常來說,處理一個方法,運行時尋找匹配的selector然后執行之。有時,你只想在運行時才創建某個方法,比如有些信息只有在運行時才能得到。要實現這個效果,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果確實增加了一個方法,記得返回YES。
- 那Cocoa在什么場景下會使用這些方法呢?Core Data用得很多。NSManagedObjects有許多在運行時添加的屬性用來處理get/set屬性和關系。那如果Model在運行時被改變了呢?
消息轉發
-
如果 resolve method 返回NO,運行時就進入下一步驟:消息轉發。有兩種常見用例。1) 將消息轉發到另一個可以處理該消息的object。2) 將多個消息轉發到同一個方法。
-
消息轉發分兩步。首先,運行時調用-forwardingTargetForSelector:,如果只是想把消息發送到另一個object,那么就使用這個方法,因為更高效。如果想要修改消息,那么就要使用-forwardInvocation:,運行時將消息打包成NSInvocation,然后返回給你處理。處理完之后,調用invokeWithTarget:。
-
Cocoa有幾處地方用到了消息轉發,主要的兩個地方是代理(Proxies)和響應鏈(Responder Chain)。NSProxy是一個輕量級的class,它的作用就是轉發消息到另一個object。如果想要惰性加載object的某個屬性會很有用。NSUndoManager也有用到,不過是截取消息,之后再執行,而不是轉發到其他的地方。
-
響應鏈是關于Cocoa如何處理與發送事件與行為到對應的對象。比如說,使用Cmd+C執行了copy命令,會發送-copy:到響應鏈。首先是First Responder,通常是當前的UI。如果沒有處理該消息,則轉發到下一個-nextResponder。這么一直下去直到找到能夠處理該消息的object,或者沒有找到,報錯。
使用Block作為Method IMP
- iOS 4.3帶來了很多新的runtime方法。除了對properties和protocols的加強,還帶來一組新的以 imp 開頭的方法。通常一個 IMP 是一個指向方法實現的指針,頭兩個參數為 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 帶來了block,imp_implementationWithBlock() 能讓我們使用block作為 IMP,下面這個代碼片段展示了如何使用block來添加新的方法。
- 可以看到,Objective-C 表面看起來挺簡單,但還是很靈活的,可以帶來很多可能性。動態語言的優勢在于在不擴展語言本身的情況下做很多很靈巧的事情。比如Key Value Observing,提供了優雅的API可以與已有的代碼無縫結合,而不需要新增語言級別的特性。
轉載于:https://www.cnblogs.com/Jenaral/p/5261224.html
總結
以上是生活随笔為你收集整理的RunTime 入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于Redis中交互的过程
- 下一篇: CodeForces 392C Yet