雜談轉載

1、什麼是運行時(Runtime)?

  • 運行時是蘋果提供的純C語言的開發庫(運行時是一種很是牛逼、開發中常常用到的底層技術)

2、運行時的做用?

  • 能得到某個類的全部成員變量
  • 能得到某個類的全部屬性
  • 能得到某個類的全部方法
  • 交換方法實現
  • 能動態添加一個成員變量
  • 能動態添加一個屬性
  • 能動態添加一個方法

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

  • 一、分析
#import <Foundation/Foundation.h> #import "XMGPerson.h" #import <objc/runtime.h> int main(int argc, const char * argv[]) { @autoreleasepool { // 成員變量的數量 unsigned int outCount = 0; // 得到全部的成員變量 // ivars是一個指向成員變量的指針 // ivars默認指向第0個成員變量(最前面) Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount); // 遍歷全部的成員變量 for (int i = 0; i<outCount; i++) { // 取出i位置對應的成員變量 // Ivar ivar = *(ivars + i); Ivar ivar = ivars[i]; // 得到成員變量的名字 NSLog(@"%s", ivar_getName(ivar)); } // 若是函數名中包含了copy\new\retain\create等字眼,那麼這個函數返回的數據就須要手動釋放 free(ivars); // Ivar ivar = *ivars; // Ivar ivar2 = *(ivars + 1); // NSLog(@"%s %s", ivar_getName(ivar), ivar_getName(ivar2)); // 一個Ivar就表明一個成員變量 // int *p; 指向int類型的變量 // Ivar *ivars; 指向Ivar類型的變量 } return 0; }
  • 二、獲取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);

4、iOS底層

一、The Runtime 簡單介紹

  • Objective-C是一門簡單的語言,95%是C。只是在語言層面上加了些關鍵字和語法。真正讓Objective-C如此強大的是它的運行時。它很小但卻很強大。它的核心是消息分發。

Messages

  • 執行一個方法,有些語言,編譯器會執行一些額外的優化和錯誤檢查,由於調用關係很直接也很明顯。但對於消息分發來講,就不那麼明顯了。在發消息前沒必要知道某個對象是否可以處理消息。你把消息發給它,它可能會處理,也可能轉給其餘的Object來處理。一個消息沒必要對應一個方法,一個對象可能實現一個方法來處理多條消息。
  • 在Objective-C中,消息是經過objc_msgSend()這個runtime方法及相近的方法來實現的。這個方法須要一個target,selector,還有一些參數。理論上來講,編譯器只是把消息分發變成objc_msgSend來執行。好比下面這兩行代碼是等價的。
[array insertObject:foo atIndex:5]; objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

Objects, Classes, MetaClasses

  • 大多數面向對象的語言裏有 classes 和 objects 的概念。Objects經過Classes生成。可是在Objective-C中,classes自己也是objects,也能夠處理消息,這也是爲何會有類方法和實例方法。具體來講,Objective-C中的Object是一個結構體(struct),第一個成員是isa,指向本身的class。這是在objc/objc.h中定義的。
typedef struct objc_object { Class isa; } *id;
  • object的class保存了方法列表,還有指向父類的指針。但classes也是objects,也會有isa變量,那麼它又指向哪兒呢?這裏就引出了第三個類型: metaclasses。一個 metaclass被指向class,class被指向object。它保存了全部實現的方法列表,以及父類的metaclass。若是想更清楚地瞭解objects,classes以及metaclasses是如何一塊兒工做地,能夠閱讀這篇文章。

Methods, Selectors and IMPs

  • 咱們知道了運行時會發消息給對象。咱們也知道一個對象的class保存了方法列表。那麼這些消息是如何映射到方法的,這些方法又是如何被執行的呢?less

  • 第一個問題的答案很簡單。class的方法列表實際上是一個字典,key爲selectors,IMPs爲value。一個IMP是指向方法在內存中的實現。很重要的一點是,selector和IMP之間的關係是在運行時才決定的,而不是編譯時。這樣咱們就能玩出些花樣。函數

  • IMP一般是指向方法的指針,第一個參數是self,類型爲id,第二個參數是_cmd,類型爲SEL,餘下的是方法的參數。這也是self和_cmd被定義的地方。下面演示了Method和IMP優化

- (id)doSomethingWithInt:(int)aInt{} id doSomethingWithInt(id self, SEL _cmd, int aInt){}
  • 如今咱們知道了objects,classes,selectors,IMPs以及消息分發,那麼運行時到底能作什麼呢?

運行時到底能作什麼呢?

  • 做用:ui

    • 建立、修改、自省classes和objects
    • 消息分發
  • 以前已經提過消息分發,不過這只是一小部分功能。全部的運行時方法都有特定的前綴。下面是一些有意思的方法:atom

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_getName, method_getImplementation, method_getReturnType等等
  • 也有一些修改的方法,包括:
    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.1 Classes And Selectors From Strings

  • 比較基礎的一個動態特性是經過String來生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起來很簡單:spa

    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,而後調用之。如下是兩種實現方式:code

- (void)parseObject:(id)object { for (id data in object) { if ([[data type] isEqualToString:@"String"]) { [self parseString:[data value]]; } else if ([[data type] isEqualToString:@"Number"]) { [self parseNumber:[data value]]; } else if ([[data type] isEqualToString:@"Array"]) { [self parseArray:[data value]]; } } } - (void)parseObjectDynamic:(id)object { for (id data in object) { [self performSelector:NSSelectorFromString([NSString stringWithFormat:@"parse%@:", [data type]]) withObject:[data value]]; } } - (void)parseString:(NSString *)aString {} - (void)parseNumber:(NSString *)aNumber {} - (void)parseArray:(NSString *)aArray {}
  • 可一看到,你能夠把7行帶if的代碼變成1行。未來若是有新的類型,只需增長實現方法便可,而不用再去添加新的 else if。

2.2 Method Swizzling

  • 以前咱們講過,方法由兩個部分組成。Selector至關於一個方法的id;IMP是方法的實現。這樣分開的一個便利之處是selector和IMP之間的對應關係能夠被改變。好比一個 IMP 能夠有多個 selectors 指向它。orm

  • 而 Method Swizzling 能夠交換兩個方法的實現。或許你會問「什麼狀況下會須要這個呢?」。咱們先來看下Objective-C中,兩種擴展class的途徑。首先是 subclassing。你能夠重寫某個方法,調用父類的實現,這也意味着你必須使用這個subclass的實例,但若是繼承了某個Cocoa class,而Cocoa又返回了原先的class(好比 NSArray)。這種狀況下,你會想添加一個方法到NSArray,也就是使用Category。99%的狀況下這是OK的,但若是你重寫了某個方法,就沒有機會再調用原先的實現了。

  • Method Swizzling 能夠搞定這個問題。你能夠重寫某個方法而不用繼承,同時還能夠調用原先的實現。一般的作法是在category中添加一個方法(固然也能夠是一個全新的class)。能夠經過method_exchangeImplementations這個運行時方法來交換實現。來看一個demo,這個demo演示瞭如何重寫addObject:方法來紀錄每個新添加的對象。

#import <objc/runtime.h> @interface NSMutableArray (LoggingAddObject) - (void)logAddObject:(id)aObject; @end @implementation NSMutableArray (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 { [self logAddObject:aObject]; NSLog(@"Added object %@ to array %@", aObject, self); } @end
  • 咱們把方法交換放到了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。

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSelector]; }
  • 那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來添加新的方法。
IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) { NSLog(@"Hello %@", string); }); class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");
  • 能夠看到,Objective-C 表面看起來挺簡單,但仍是很靈活的,能夠帶來不少可能性。動態語言的優點在於在不擴展語言自己的狀況下作不少很靈巧的事情。好比Key Value Observing,提供了優雅的API能夠與已有的代碼無縫結合,而不須要新增語言級別的特性。
相關文章
相關標籤/搜索