運(yùn)行時(iOS)

一贾惦、什么是運(yùn)行時(Runtime)?

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

二、運(yùn)行時的作用龄章?

  • 能獲得某個類的所有成員變量
  • 能獲得某個類的所有屬性
  • 能獲得某個類的所有方法
  • 交換方法實(shí)現(xiàn)
  • 能動態(tài)添加一個成員變量
  • 能動態(tài)添加一個屬性
  • 能動態(tài)添加一個方法

三吃谣、案例:運(yùn)行時獲取成員變量名稱

  • 1、分析
#import <Foundation/Foundation.h>
#import "XMGPerson.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 成員變量的數(shù)量
        unsigned int outCount = 0;

        // 獲得所有的成員變量
        // ivars是一個指向成員變量的指針
        // ivars默認(rèn)指向第0個成員變量(最前面)
        Ivar *ivars = class_copyIvarList([XMGPerson class], &outCount);

        // 遍歷所有的成員變量
        for (int i = 0; i<outCount; i++) {
            // 取出i位置對應(yīng)的成員變量
//            Ivar ivar = *(ivars + i);
            Ivar ivar = ivars[i];
            // 獲得成員變量的名字
            NSLog(@"%s", ivar_getName(ivar));
        }

        // 如果函數(shù)名中包含了copy\new\retain\create等字眼做裙,那么這個函數(shù)返回的數(shù)據(jù)就需要手動釋放
        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;
}
  • 2岗憋、獲取UITextFiled成員變量的名稱
Snip20151027_1.png
 // 成員變量的數(shù)量
    unsigned int outCount = 0;

    // 獲得所有的成員變量
    Ivar *ivars = class_copyIvarList([UITextField class], &outCount);

    // 遍歷所有的成員變量
    for (int i = 0; i<outCount; i++) {
        // 取出i位置對應(yīng)的成員變量
        Ivar ivar = ivars[i];
        // 獲得成員變量的名字
        NSLog(@"%s", ivar_getName(ivar));
    }

    // 如果函數(shù)名中包含了copy\new\retain\create等字眼,那么這個函數(shù)返回的數(shù)據(jù)就需要手動釋放
    free(ivars);

四锚贱、iOS底層

1仔戈、The Runtime 簡單介紹

  • Objective-C是一門簡單的語言,95%是C拧廊。只是在語言層面上加了些關(guān)鍵字和語法监徘。真正讓Objective-C如此強(qiáng)大的是它的運(yùn)行時。它很小但卻很強(qiáng)大吧碾。它的核心是消息分發(fā)凰盔。

Messages

  • 執(zhí)行一個方法,有些語言倦春,編譯器會執(zhí)行一些額外的優(yōu)化和錯誤檢查户敬,因?yàn)檎{(diào)用關(guān)系很直接也很明顯落剪。但對于消息分發(fā)來說,就不那么明顯了尿庐。在發(fā)消息前不必知道某個對象是否能夠處理消息忠怖。你把消息發(fā)給它,它可能會處理抄瑟,也可能轉(zhuǎn)給其他的Object來處理凡泣。一個消息不必對應(yīng)一個方法,一個對象可能實(shí)現(xiàn)一個方法來處理多條消息锐借。

  • 在Objective-C中问麸,消息是通過objc_msgSend()這個runtime方法及相近的方法來實(shí)現(xiàn)的。這個方法需要一個target钞翔,selector严卖,還有一些參數(shù)。理論上來說布轿,編譯器只是把消息分發(fā)變成objc_msgSend來執(zhí)行哮笆。比如下面這兩行代碼是等價的。

[array insertObject: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萝毛,也可以處理消息项阴,這也是為什么會有類方法和實(shí)例方法。具體來說笆包,Objective-C中的Object是一個結(jié)構(gòu)體(struct)环揽,第一個成員是isa,指向自己的class庵佣。這是在objc/objc.h中定義的歉胶。
typedef struct objc_object {
    Class isa;
} *id;
  • object的class保存了方法列表,還有指向父類的指針巴粪。但classes也是objects通今,也會有isa變量,那么它又指向哪兒呢肛根?這里就引出了第三個類型: metaclasses辫塌。一個 metaclass被指向class,class被指向object派哲。它保存了所有實(shí)現(xiàn)的方法列表臼氨,以及父類的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地狮辽,可以閱讀這篇文章一也。

Methods, Selectors and IMPs

  • 我們知道了運(yùn)行時會發(fā)消息給對象。我們也知道一個對象的class保存了方法列表喉脖。那么這些消息是如何映射到方法的椰苟,這些方法又是如何被執(zhí)行的呢?

  • 第一個問題的答案很簡單树叽。class的方法列表其實(shí)是一個字典舆蝴,key為selectors,IMPs為value题诵。一個IMP是指向方法在內(nèi)存中的實(shí)現(xiàn)。很重要的一點(diǎn)是性锭,selector和IMP之間的關(guān)系是在運(yùn)行時才決定的赠潦,而不是編譯時。這樣我們就能玩出些花樣草冈。

  • IMP通常是指向方法的指針她奥,第一個參數(shù)是self,類型為id怎棱,第二個參數(shù)是_cmd哩俭,類型為SEL,余下的是方法的參數(shù)拳恋。這也是self和_cmd被定義的地方凡资。下面演示了Method和IMP

- (id)doSomethingWithInt:(int)aInt{}

id doSomethingWithInt(id self, SEL _cmd, int aInt){}
  • 現(xiàn)在我們知道了objects,classes,selectors,IMPs以及消息分發(fā),那么運(yùn)行時到底能做什么呢谬运?

運(yùn)行時到底能做什么呢隙赁?

  • 作用:

    • 創(chuàng)建、修改吩谦、自省classes和objects
    • 消息分發(fā)
  • 之前已經(jīng)提過消息分發(fā)鸳谜,不過這只是一小部分功能。所有的運(yùn)行時方法都有特定的前綴式廷。下面是一些有意思的方法:

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

- 一些通用的自省方法
```objc
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有點(diǎn)像classes早处,但是精簡版的湾蔓,運(yùn)行時的方法是一樣的。你可以獲取method, property, protocol列表, 檢查是否實(shí)現(xiàn)了其他的protocol砌梆。

sel

  • 最后我們有一些方法可以處理 selectors卵蛉,比如獲取名字,注冊一個selector等等么库。

2傻丝、運(yùn)行時能干什么?(舉例)

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注竿,如果運(yùn)行時不存在該class的話茄茁。

  • 另一個使用場景是根據(jù)不同的輸入返回不同的class或method。比如你在解析一些數(shù)據(jù)巩割,每個數(shù)據(jù)項都有要解析的字符串以及自身的類型(String裙顽,Number,Array)宣谈。你可以在一個方法里搞定這些愈犹,也可以使用多個方法。其中一個方法是獲取type闻丑,然后使用if來調(diào)用匹配的方法漩怎。另一種是根據(jù)type來生成一個selector勋颖,然后調(diào)用之。以下是兩種實(shí)現(xiàn)方式:

- (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行牙言。將來如果有新的類型,只需增加實(shí)現(xiàn)方法即可怪得,而不用再去添加新的 else if。

2.2 Method Swizzling

  • 之前我們講過卑硫,方法由兩個部分組成徒恋。Selector相當(dāng)于一個方法的id;IMP是方法的實(shí)現(xiàn)欢伏。這樣分開的一個便利之處是selector和IMP之間的對應(yīng)關(guān)系可以被改變入挣。比如一個 IMP 可以有多個 selectors 指向它。

  • 而 Method Swizzling 可以交換兩個方法的實(shí)現(xiàn)硝拧【斗ぃ或許你會問“什么情況下會需要這個呢?”障陶。我們先來看下Objective-C中滋恬,兩種擴(kuò)展class的途徑。首先是 subclassing抱究。你可以重寫某個方法恢氯,調(diào)用父類的實(shí)現(xiàn),這也意味著你必須使用這個subclass的實(shí)例鼓寺,但如果繼承了某個Cocoa class勋拟,而Cocoa又返回了原先的class(比如 NSArray)。這種情況下妈候,你會想添加一個方法到NSArray敢靡,也就是使用Category。99%的情況下這是OK的苦银,但如果你重寫了某個方法啸胧,就沒有機(jī)會再調(diào)用原先的實(shí)現(xiàn)了。

  • Method Swizzling 可以搞定這個問題幔虏。你可以重寫某個方法而不用繼承吓揪,同時還可以調(diào)用原先的實(shí)現(xiàn)。通常的做法是在category中添加一個方法(當(dāng)然也可以是一個全新的class)所计∧牵可以通過method_exchangeImplementations這個運(yùn)行時方法來交換實(shí)現(xiàn)。來看一個demo主胧,這個demo演示了如何重寫addObject:方法來紀(jì)錄每一個新添加的對象叭首。

#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中习勤,這個方法只會被調(diào)用一次,而且是運(yùn)行時載入焙格。如果指向臨時用一下图毕,可以放到別的地方。注意到一個很明顯的遞歸調(diào)用logAddObject:眷唉。這也是Method Swizzling容易把我們搞混的地方予颤,因?yàn)槲覀円呀?jīng)交換了方法的實(shí)現(xiàn),所以其實(shí)調(diào)用的是addObject:

動態(tài)繼承冬阳、交換

  • 我們可以在運(yùn)行時創(chuàng)建新的class蛤虐,這個特性用得不多,但其實(shí)它還是很強(qiáng)大的肝陪。你能通過它創(chuàng)建新的子類驳庭,并添加新的方法。

  • 但這樣的一個子類有什么用呢氯窍?別忘了Objective-C的一個關(guān)鍵點(diǎ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ù)情況下確實(shí)如此计济,但Cocoa和runtime也提供了一些應(yīng)對方法。

+ 首先是動態(tài)方法處理排苍。通常來說沦寂,處理一個方法,運(yùn)行時尋找匹配的selector然后執(zhí)行之淘衙。有時传藏,你只想在運(yùn)行時才創(chuàng)建某個方法,比如有些信息只有在運(yùn)行時才能得到。要實(shí)現(xiàn)這個效果毯侦,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:哭靖。如果確實(shí)增加了一個方法,記得返回YES侈离。

```objc
+ (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有許多在運(yùn)行時添加的屬性用來處理get/set屬性和關(guān)系卦碾。那如果Model在運(yùn)行時被改變了呢铺坞?

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

  • 如果 resolve method 返回NO,運(yùn)行時就進(jìn)入下一步驟:消息轉(zhuǎn)發(fā)洲胖。有兩種常見用例济榨。1) 將消息轉(zhuǎn)發(fā)到另一個可以處理該消息的object。2) 將多個消息轉(zhuǎn)發(fā)到同一個方法宾濒。

  • 消息轉(zhuǎn)發(fā)分兩步。首先屏箍,運(yùn)行時調(diào)用-forwardingTargetForSelector:绘梦,如果只是想把消息發(fā)送到另一個object,那么就使用這個方法赴魁,因?yàn)楦咝斗睢H绻胍薷南ⅲ敲淳鸵褂?forwardInvocation:颖御,運(yùn)行時將消息打包成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的加強(qiáng)婴渡,還帶來一組新的以 imp 開頭的方法。通常一個 IMP 是一個指向方法實(shí)現(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)勢在于在不擴(kuò)展語言本身的情況下做很多很靈巧的事情啃沪。比如Key Value Observing粘拾,提供了優(yōu)雅的API可以與已有的代碼無縫結(jié)合,而不需要新增語言級別的特性创千。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缰雇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子追驴,更是在濱河造成了極大的恐慌械哟,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殿雪,死亡現(xiàn)場離奇詭異戒良,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)冠摄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門糯崎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人河泳,你說我怎么就攤上這事沃呢。” “怎么了拆挥?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵薄霜,是天一觀的道長剪廉。 經(jīng)常有香客問我艾帐,道長姿锭,這世上最難降的妖魔是什么茸炒? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮崎坊,結(jié)果婚禮上备禀,老公的妹妹穿的比我還像新娘。我一直安慰自己奈揍,他們只是感情好曲尸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著男翰,像睡著了一般另患。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蛾绎,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天昆箕,我揣著相機(jī)與錄音,去河邊找鬼租冠。 笑死鹏倘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肺稀。 我是一名探鬼主播第股,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼应民,長吁一口氣:“原來是場噩夢啊……” “哼话原!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起诲锹,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤繁仁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后归园,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黄虱,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年庸诱,在試婚紗的時候發(fā)現(xiàn)自己被綠了捻浦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡桥爽,死狀恐怖朱灿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钠四,我是刑警寧澤盗扒,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響侣灶,放射性物質(zhì)發(fā)生泄漏甸祭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一褥影、第九天 我趴在偏房一處隱蔽的房頂上張望池户。 院中可真熱鬧,春花似錦伪阶、人聲如沸煞檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斟湃。三九已至,卻和暖如春檐薯,著一層夾襖步出監(jiān)牢的瞬間凝赛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工坛缕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留墓猎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓赚楚,卻偏偏與公主長得像毙沾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宠页,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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