Objective-C Runtime(四)isa swizzling

Runtime 4 isa swizzling

Objective-C Runtime(一)

  • 簡(jiǎn)介
  • 對(duì)象、類的結(jié)構(gòu)
    • objc_object
    • objc_class
  • 消息傳遞(Messaging)
    • objc_method
    • objc_msgSend

Objective-C Runtime(二)

  • 動(dòng)態(tài)方法解析和轉(zhuǎn)發(fā)
    • 動(dòng)態(tài)方法解析
    • 快速消息轉(zhuǎn)發(fā)
    • 標(biāo)準(zhǔn)消息轉(zhuǎn)發(fā)
    • 消息轉(zhuǎn)發(fā)與多繼承
    • 消息轉(zhuǎn)發(fā)與代理對(duì)象

Objective-C Runtime(三)

  • Method Swizzling
    • class_replaceMethod
    • method_setImplementation
    • method_exchangeImplementations
    • Method Swizzling 的應(yīng)用
    • Method Swizzling 注意事項(xiàng)

Objective-C Runtime(四)

  • isa swizzling
    • 介紹
    • 應(yīng)用之KVO
    • 注意

持續(xù)更新中...

介紹

對(duì)比上一篇 Runtime 3 Method Swizzling, isa swizzling 顧名思義就是把對(duì)象的 isa 指針進(jìn)行替換。

根據(jù)第一篇 Runtime 1 簡(jiǎn)介,對(duì)象、類的結(jié)構(gòu),消息傳遞,我們知道對(duì)象都有 isa 指針指向它的類,消息傳遞時(shí)也通過(guò)isa指針找到類中所對(duì)應(yīng)的方法。更改對(duì)象的 isa 指針,不僅改變了它所屬于的類型,也更改了它的行為(方法)。

舉個(gè)例子:

@interface Father: NSObject
@property (nonatomic, assign) NSInteger f;
@end
@implementation Father
@end

@interface Child: Father
@property (nonatomic, assign) NSInteger c;
@end
@implementation Child
@end

然后執(zhí)行:

//1
Child *child = [[Child alloc] init];
Class class = object_getClass(child); //Child
//2
NSLog(@"%ld, %ld", child.c, child.f);  //0, 0
//3
object_setClass(child, [NSObject class]);
class = object_getClass(child); //NSObject
//4
NSLog(@"%ld, %ld", child.c, child.f);  //error: -[NSObject c]: unrecognized selector sent to instance 0x60400002aaa0

上面的代碼就是將 child 對(duì)象進(jìn)行 isa swizzling,具體步驟分析如下:

  1. 上面代碼創(chuàng)建了實(shí)例 child,它的 isa 指向 Child 類。
  2. child.cchild.f 是通過(guò)消息傳遞找到方法實(shí)現(xiàn)的,通過(guò) child 的 isa 指針找到它的 Child 類,然后在 Child 類中的 method list 找 c。f 方法的查找同理,只是多了一步在 Child 類中找不到,則往它的 superclass 中找。
  3. object_setClass(child, [NSObject class]) 方法將 child 對(duì)象的 isa 指向 NSObject。
  4. 這時(shí)再進(jìn)行消息傳遞時(shí),查找的是 child 對(duì)象的 isa 指向的 NSObject 類,由于 NSObject 類沒(méi)有對(duì)應(yīng)的 c 和 f 方法,最終找不到方法程序崩潰。

應(yīng)用之KVO

KVO在調(diào)用存取方法之前總是調(diào)用 willChangeValueForKey: ,之后總是調(diào)用 didChangeValueForkey: 。怎么做到的呢?答案是通過(guò) isa 混寫(xiě)(isa-swizzling)。

Apple 的文檔對(duì) KVO 實(shí)現(xiàn)的描述:

Automatic key-value observing is implemented using a technique called isa-swizzling.

...

When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class.

...

從Apple 的文檔可以看出:Apple 并不希望過(guò)多暴露 KVO 的實(shí)現(xiàn)細(xì)節(jié)。不過(guò),要是借助 runtime 提供的方法去深入挖掘,所有被掩蓋的細(xì)節(jié)都會(huì)原形畢露:

當(dāng)你觀察一個(gè)對(duì)象時(shí),一個(gè)新的類會(huì)被動(dòng)態(tài)創(chuàng)建。這個(gè)類繼承自該對(duì)象的原本的類,并重寫(xiě)了被觀察屬性的 setter 方法。重寫(xiě)的 setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對(duì)象:值的更改。最后通過(guò) isa 混寫(xiě)(isa-swizzling) 把這個(gè)對(duì)象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對(duì)象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類,對(duì)象就神奇的變成了新創(chuàng)建的子類的實(shí)例。我畫(huà)了一張示意圖,如下所示:

isa_swizzling_kvo.png

然而 KVO 在實(shí)現(xiàn)中使用了 isa-swizzling 的確不是很容易發(fā)現(xiàn):Apple 還重寫(xiě)了-class方法并返回原來(lái)的類。企圖欺騙我們:這個(gè)類沒(méi)有變,就是原本那個(gè)類。。。如下:

Father *father = [[Father alloc] init];
[father addObserver:self forKeyPath:@"f" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"%@", object_getClass(father)); //NSKVONotifying_Father
NSLog(@"%@", father.class); //Father

假設(shè)“被監(jiān)聽(tīng)的對(duì)象”的類對(duì)象是 MYClass ,有時(shí)候我們能看到對(duì) NSKVONotifying_MYClass 的引用而不是對(duì) MYClass 的引用。借此我們得以知道 Apple 使用了 isa 混寫(xiě)(isa-swizzling)。具體探究過(guò)程可參考 這篇博文 。

由下面執(zhí)行過(guò)程可知,通過(guò)KVO生成的中間類繼承原來(lái)的類:

Class kvoClass = object_getClass(father);
Class kvoSuperClass = class_getSuperclass(kvoClass);
NSLog(@"%@", kvoSuperClass); //Father

注意

isa-swizzling 改變了對(duì)象說(shuō)屬類型,因此更改范圍比 method-swizzling 范圍更廣,使用時(shí)要更加注意。

runtime 極其尖銳,選擇使用 runtime 時(shí)要清楚每一步的真正原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容