Objective-C的哪些特性利用了運行時機制

這是我遇到的一道面試題。雖然當時回答得一般,但是我很喜歡這道題目,因為它可以考察出面試者對運行時機制理解的深度和廣度,所以這里想試著重新回答一下。

現在的回答可能還很淺薄,如果以后想到了什么能補充的地方也會更新這篇( ̄▽ ̄)

本文中關于runtime的源碼參考自蘋果開源的objc4-680


Method Swizzling



在一些靜態語言比如C語言中,調用一個函數后會跳到哪個地址執行哪些指令,是在編譯鏈接的時候確定的。而在Objective-C中,向一個對象發送消息時,具體會執行哪個方法,則是運行時系統根據selector查找對應的IMP得到的。

所以通過交換兩個selector對應的IMP,可以完成對執行的具體方法的調換。


Associated Object



使用

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object)

這三個方法,可以在運行時為一個對象建立一個關聯對象。程序員可以為現有的類增添關聯對象,起到類似于動態添加“實例變量”的作用。

可以在蘋果開源的runtime代碼中找到這幾個方法的實現。比如在objc-references.mm中可以找到objc_setAssociatedObject最終調用的方法:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) 

閱讀這幾個方法的實現,可以大概了解到關聯對象的實現機制。它維護了一個全局的AssociationsHashMap對象。這是一個map對象,其中鍵為被關聯對象的地址,值為一個指向ObjectAssociationMap對象的指針。而ObjectAssociationMap類派生自std::map,鍵為傳入的“key”參數(是一個地址),值為一個指向ObjcAssociation對象的指針。最后,ObjcAssociation中的成員變量_value指的就是那個關聯對象。


Category



在Objective-C中,因為一個類中有哪些方法是在運行時決定的,所以可以用category給任何類增加方法。

objc-runtime-new.mm中,可以看到methodizeClass方法

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{

......

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

........

}

根據注釋,這個方法負責將一個類的方法列表、協議列表和屬性列表整理好。在這個類的實現中,我們可以看到,相比于整理類自身的方法列表、協議列表和屬性列表,將category中的內容關聯到對應的類上,是之后做的事情。

查看attachCategories的方法實現,其中,category中的方法列表是這樣關聯到對應的類中的:

rw->methods.attachLists(mlists, mcount);

在category將方法列表關聯到類的方法列表的過程中,是不會判斷selector是否重名的。如果類A中已經定義了方法demoMethod,那么在A的category中定義重名的demoMethod,會導致加載完成后,類A的方法列表中有兩個名為demoMethod的方法。可以做個小實驗驗證:

unsigned int count = 0;
Method *methodList = class_copyMethodList([A class], &count);
Method *methodHeader = methodList;
while (count--) {
    Method method = *methodHeader;
    SEL selector = method_getName(method);
    NSLog(@"sel: %@", NSStringFromSelector(selector));
    methodHeader++;
}
free(methodList);

這段代碼的輸出:

2016-06-26 18:31:04.251 XSQCategoryDemo[3410:5607532] sel: demoMethod
2016-06-26 18:31:04.252 XSQCategoryDemo[3410:5607532] sel: demoMethod

即表示,類A的方法列表中有兩個名為demoMethod的方法。


KVO



KVO的實現原理是isa-swizzling

官方文檔中解釋了:當一個觀察者被注冊到一個對象上的時候,這個對象的isa指針會被更改,指向一個中間類而非真正的類。

如果做實驗驗證一下:

A *a = [[A alloc] init];
NSLog(@"%@", object_getClass(a));
[a addObserver:someObserver forKeyPath:@"someKeyPath" options:0 context:nil];
NSLog(@"%@", object_getClass(a));

這一小段代碼的輸出是:

2016-06-25 23:56:49.381 XSQKVODemo[88055:5252219] A
2016-06-25 23:56:52.161 XSQKVODemo[88055:5252219] NSKVONotifying_A

可以看出,在注冊了觀察者之后,對象指向的類發生了改變。


KVC



在Foundation的NSKeyValueCoding.h中,幾個KVO方法都用詳細的注釋介紹了它的搜索策略。比如

- (nullable id)valueForKey:(NSString *)key;

方法是這樣執行的:

  1. 搜索消息接收者所在類的accessor方法,依次尋找名字能匹配
    -get<Key>-<key>或者-is<Key>的方法,如果找到則調用;
  2. 搜索消息接收者所在類的方法,如果有能匹配NSOrderedSet的方法,則會返回一個代理對象,這個代理對象可以接收所有NSOrderedSet對象可以接收的消息;
  3. 搜索消息接收者所在類的方法,如果能匹配NSArray的方法,則返回一個可以接收所有NSArray對象可以接收的消息的代理對象;
  4. 搜索消息接收者所在類的方法,如果能匹配NSSet的方法,則返回一個可以接收所有NSSet對象可以接收的消息的代理對象;
  5. 如果消息接收者所在的類的+accessInstanceVariablesDirectly方法返回的是YES,就依次尋找名字能匹配
    _<key>_is<Key><key>或者is<Key>的實例變量;
  6. 最后,調用-valueForUndefinedKey:然后返回結果。

雖然沒找到明確的文檔說KVC利用了運行時機制,但類似這樣對方法名、對實例變量的搜索,沒有運行時系統的支持應該是無法做到的。

其實如果在Xcode中設置一個符號斷點class_getInstanceMethod,也可以調試到這個方法被KVC的一些方法調用了:


參考

objc4-680
Objective-C Runtime Reference
Key-Value Observing Implementation Details
刨根問底Objective-C Runtime(3)- 消息 和 Category
iOS 程序 main 函數之前發生了什么
Objective-C Associated Objects 的實現原理
objc category的秘密
Key-Value Coding and Observing
深入理解Objective-C:Category

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,698評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,202評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,742評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,580評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,297評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,688評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,693評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,875評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,438評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,183評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,384評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,931評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,612評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,022評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,297評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,093評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,330評論 2 377

推薦閱讀更多精彩內容