一探究竟:Objective-C isa指針及KVO實(shí)現(xiàn)原理

1、什么是isa指針
概念:

Every object has an isa instance variable that identifies the object's class. The runtime uses this pointer to determine the actual class of the object when it needs to.

每個(gè)對(duì)象都有一個(gè)標(biāo)識(shí)對(duì)象類的isa實(shí)例變量。運(yùn)行時(shí)使用此指針來確定對(duì)象需要時(shí)的實(shí)際類。
這就好比把isa拆開成 is a(是什么類的實(shí)例)的意思。

代碼中的isa:

在objc.h文件中有這樣定義:


圖1

從圖中我們可以看出三點(diǎn):
1、 id類型是一個(gè)objc_object結(jié)構(gòu)體的指針。
2、objc_object結(jié)構(gòu)體包含一個(gè)Class 類型的變量isa。
3、 Class是objc_class結(jié)構(gòu)體的指針。

事實(shí)上在objc的runtime中,類是用 objc_class 結(jié)構(gòu)體表示的,對(duì)象是用 objc_object 結(jié)構(gòu)體表示的。這也就解釋了為什么id類型可以指向OC中任意對(duì)象類型了。

到了這里我們只需要再明白o(hù)bjc_class結(jié)構(gòu)體的內(nèi)容就可以了。在runtime.h文件中objc_class結(jié)構(gòu)體定義如下:

struct objc_class {
    Class isa  //所屬類的指針
    Class super_class//指向父類的指針                                        
    const char *name    //類名                                     
    long version            // 版本                                 
    long info                   //供運(yùn)行期使用的一些位標(biāo)識(shí)。                             
    long instance_size      //實(shí)例大小                                 
    struct objc_ivar_list *ivars       //成員變量數(shù)組                      
    struct objc_method_list **methodLists  //方法列表                  
    struct objc_cache *cache//指向最近使用的方法.用于方法調(diào)用的優(yōu)化                            
    struct objc_protocol_list *protocols//協(xié)議的數(shù)組                     
} 

當(dāng)看到objc_class結(jié)構(gòu)體的第一個(gè)變量也是Class類型的指針時(shí),是不是很崩潰。不必難過,其實(shí)這正好驗(yàn)證了萬物皆對(duì)象的事實(shí)。類也是對(duì)象,他是meteClass(元類)的實(shí)例
到這里我們來整理下思路:

  • 實(shí)例對(duì)象在運(yùn)行時(shí)被表示成objc_object類型結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部有個(gè)isa指針指向objc_class結(jié)構(gòu)體。
  • objc_class內(nèi)部保存了類的變量和方法列表以及其他一些信息,并且還有一個(gè)isa指針。這個(gè)isa指針會(huì)指向meteClass(元類),元類里保存了這個(gè)類的類方法列表。
  • 為了完整性,其實(shí)元類里也有一個(gè)isa指針,這個(gè)isa指針,指向的是根元類,根元類的isa指針指向自己
    大致如下面邏輯:
    實(shí)例對(duì)象--(runtime)-->objc_object--(isa)-->objc_class--(isa)-->元類--isa-->根元類--isa-->自己。
    然而值得一提的是:當(dāng)我們調(diào)用某個(gè)類的方法時(shí),如果這個(gè)類的方法列表里沒有該方法,則會(huì)去找這個(gè)類的父類的方法列表。這種機(jī)制就是通過objc_class的第二個(gè)變量super_class指針實(shí)現(xiàn)的。并且這種繼承關(guān)系會(huì)擴(kuò)展到元類。最終類似于下圖的一種關(guān)系:
關(guān)系圖
2、KVO的實(shí)現(xiàn)原理

key-value observing is implemented using a technique called isa-swizzling.
The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
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. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.

從Apple文檔中我們大概可以了解到:
KVO是通過"isa-swizzling"技術(shù)來實(shí)現(xiàn)的,當(dāng)一個(gè)對(duì)象注冊(cè)觀察者時(shí),這個(gè)對(duì)象的isa指針被修改指向一個(gè)中間類。
KVO 的實(shí)現(xiàn)依賴于 Objective-C 強(qiáng)大的 runtime(這里不詳細(xì)講解,我準(zhǔn)備開一篇結(jié)合實(shí)例講解下runtime,喜歡我文章的可以關(guān)注下O(∩_∩)O~~)。當(dāng)觀察A類型的對(duì)象時(shí),在運(yùn)行時(shí)會(huì)創(chuàng)建了一個(gè)集成自A類的NSKVONotifying_A類,且為NSKVONotifying_A重寫觀察屬性的setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察者屬性值的更改情況。
假設(shè)A類有個(gè)name屬性,NSKVONotifying_A重寫setName方法:

- (void) setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];
    [super setName:name];
    [self didChangeValueForKey:@"name"];
}

被觀察屬性發(fā)生改變之前,willChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值即將變更,來保存舊值;當(dāng)改變發(fā)生后,didChangeValueForKey:被調(diào)用,通知系統(tǒng)該 keyPath 的屬性值已經(jīng)變更;之后,observeValueForKey:ofObject:change:context:就會(huì)被調(diào)用。

3、手動(dòng)觸發(fā)KVO

以上我們說的都是自動(dòng)觸發(fā),只要我們注冊(cè)了觀察者。只要被觀察的屬性值一改變就會(huì)調(diào)用observeValueForKey:ofObject:change:context:方法,而有時(shí)候我們?cè)谔囟ǖ那闆r下,才去通知觀察者被觀察的屬性改變了。這就需要我們手動(dòng)觸發(fā)KVO。大致步驟:
1、取消自動(dòng)觸發(fā):重寫+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
2、重寫屬性的setter方法,根據(jù)需求判斷是否需要調(diào)用willChangeValueForKey:didChangeValueForKey:方法。
代碼如下:

- (void)setName:(NSString *)name{
    
    if ([name isEqualToString:@"小白"]) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }else{
         _name = name;
    }
   
}

+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
    // 這里只是取消了name屬性的自動(dòng)觸發(fā)
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

這樣寫之后,只有當(dāng)Dog類對(duì)象的name屬性等于"小白"時(shí)才會(huì)通知觀察者屬性改變了。
注意:取消自動(dòng)觸發(fā)的方法如果直接返回NO,那么這個(gè)類的對(duì)象的所有屬性值都會(huì)取消自動(dòng)觸發(fā)。所以最好根據(jù)需要自己判斷。


從上面的介紹中我們看到KVO的調(diào)用其實(shí)很繁瑣,如果有個(gè)帶有block的方法就好了,這篇文章手把手教你封裝一個(gè)帶有block的KVO方法。
-------------完--------------
最后:喜歡我文章的可以多多點(diǎn)贊和關(guān)注,您的鼓勵(lì)是我寫作的動(dòng)力。O(∩_∩)O~

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、什么是isa指針 概念: Every object has an isa instance variable ...
    LQWAWAIOS閱讀 308評(píng)論 0 3
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,101評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 842評(píng)論 0 2
  • 文中的實(shí)驗(yàn)代碼我放在了這個(gè)項(xiàng)目中。 以下內(nèi)容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 1,030評(píng)論 0 6
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 870評(píng)論 0 4

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