KVO,ioskvo

來源:互聯網
上載者:User

KVO,ioskvo
1.KVO概念

KVO即索引值觀察,它提供一種機制,當被觀察的對象的屬性發生改變後,對象會接收到通知,從而做出相應的改變。

2.KVO實現原理

  這裡要說一個isa指標,在Objective-C中,任何類的定義都是對象。類和類的執行個體(對象)沒有任何本質上的區別。任何對象都有isa指標。

  那麼什麼是類呢?在xcode中用快速鍵Shift+Cmd+O 開啟檔案objc.h 能看到類的定義:

  

  可以看出:

  Class 是一個 objc_class 結構類型的指標, id是一個 objc_object 結構類型的指標.

  我們再來看看 objc_class 的定義:

 

 

  稍微解釋一下各個參數的意思:

  isa:是一個Class 類型的指標. 每個執行個體對象有個isa的指標,他指向對象的類,而Class裡也有個isa的指標, 指向meteClass(元類)。元類儲存了類方法的列表。當類方法被調用時,先會從本身尋找類方法的實現,如果沒有,元類會向他父類尋找該方法。同時注意的是:元類(meteClass)也是類,它也是對象。元類也有isa指標,它的isa指標最終指向的是一個根元類(root meteClass).根元類的isa指標指向本身,這樣形成了一個封閉的內迴圈。

  super_class:父類,如果該類已經是最頂層的根類,那麼它為NULL。

  version:類的版本資訊,預設為0

  info:供運行期使用的一些位標識。

  instance_size:該類的執行個體變數大小

  ivars:成員變數的數組

再來看看各個類執行個體變數的繼承關係:

 

  每一個對象本質上都是一個類的執行個體。其中類定義了成員變數和成員方法的列表。對象通過對象的isa指標指向類。

  每一個類本質上都是一個對象,類其實是元類(meteClass)的執行個體。元類定義了類方法的列表。類通過類的isa指標指向元類。

  所有的元類最終繼承一個根元類,根元類isa指標指向本身,形成一個封閉的內迴圈。

 

  原理:每一個對象都有一個isa指標,這個對象根據isa指標去尋找它所歸屬的類,當我們給一個對象註冊觀察者的時候,系統會在運行時給這個對象建立一個子類,這個子類繼承於當前對象歸屬的類,並把當前對象的isa指標指向這個子類,於是當前對象就變成了這個子類的一個執行個體。那麼這個子類內部做了什麼操作呢?其實這個子類重寫了set方法,當原對象在調用set方法賦值的時候,會根據isa指標到建立子類的方法列表去尋找set方法的IMP,此時這個重寫的set方法會對所有觀察這個屬性的對象發出通知,於是原有的對象會作出改變。

 

深入剖析:

  Apple 使用了 isa 混寫(isa-swizzling)來實現 KVO 。當觀察對象A時,KVO機制動態建立一個新的名為: NSKVONotifying_A的新類,該類繼承自對象A的本類,且KVO為NSKVONotifying_A重寫觀察屬性的setter 方法,setter 方法會負責在調用原 setter 方法之前和之後,通知所有觀察對象屬性值的更改情況。

 

  • NSKVONotifying_A類剖析:在這個過程,被觀察對象的 isa 指標從指向原來的A類,被KVO機制修改為指向系統新建立的子類 NSKVONotifying_A類,來實現當前類屬性值改變的監聽;
  • 所以當我們從應用程式層面上看來,完全沒有意識到有新的類出現,這是系統“隱瞞”了對KVO的底層實現過程,讓我們誤以為還是原來的類。但是此時如果我們建立一個新的名為“NSKVONotifying_A”的類(),就會發現系統運行到註冊KVO的那段代碼時程式就崩潰,因為系統在註冊監聽的時候動態建立了名為NSKVONotifying_A的中間類,並指向這個中間類了。
  • 因而在該對象上對 setter 的調用就會調用已重寫的 setter,從而啟用索引值通知機制。

 

  • KVO索引值觀察依賴於NSObject的兩個方法:willChangeValueForKey和didChangevlueForKey,即在索引值改變前後分別調用這兩個方法,然後在這兩個方法的中間調用父類set方法賦值。
  • 被觀察屬性發生改變之前,willChangeValueForKey:被調用,通知系統該 keyPath 的屬性值即將變更;當改變發生後, didChangeValueForKey: 被調用,通知系統該 keyPath 的屬性值已經變更;之後observeValueForKey:ofObject:change:context: 也會被調用。且重寫觀察屬性的setter 方法這種繼承方式的注入是在運行時而不是編譯時間實現的。

KVO為子類的觀察者屬性重寫調用存取方法的工作原理在代碼中相當於:

1 -(void)setName:(NSString *)newName2 {3   [self willChangeValueForKey:@"name"];    //KVO在調用存取方法之前總調用4   [super setValue:newName forKey:@"name"]; //調用父類的存取方法5   [self didChangeValueForKey:@"name"];     //KVO在調用存取方法之後總調用6 }

 

 

樣本驗證
 1 //Person類 2 @interface Person : NSObject 3 @property (nonatomic,copy) NSString *name; 4 @end 5  6 //controller 7 Person *per = [[Person alloc]init]; 8 //斷點1 9 [per addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];10 //斷點211 per.name = @"小明";12 [per removeObserver:self forKeyPath:@"name"];13 //斷點3
View Code

運行項目,

  • 斷點1位置:

 

 

  • 可以看到isa指向Person類,我們也可以使用lldb命令查看:
    (lldb) po [per class]Person(lldb) po object_getClass(per)Person(lldb)

 

  • 斷點2位置:

 

(lldb) po [per class]Person(lldb) po object_getClass(per)NSKVONotifying_Person(lldb)

 

  • 斷點3位置:
(lldb) po [per class]Person(lldb) po object_getClass(per)Person(lldb)

 

 

  上面的結果說明,在per對象被觀察時,framework使用runtime動態建立了一個Person類的子類NSKVONotifying_Person,而且為了隱藏這個行為,NSKVONotifying_Person重寫了- class方法返回之前的類,就好像什麼也沒發生過一樣。但是使用object_getClass()時就暴露了,因為這個方法返回的是這個對象的isa指標,這個指標指向的一定是個這個對象的類對象

 

3.KVO的特點

由於KVO內部實現的原理是重寫了set方法,因此只有當被觀察對象的屬性調用set方法賦值的時候才會執行KVO的的回調方法。所以如果直接對屬性的成員變數直接賦值那麼不會觸發KVO。

4.KVO的調用步驟

1.註冊觀察者
2.在回調方法中處理事件
3.移除觀察者

5.代碼實踐
 1     self.changeStr = @"您好"; 2     [self addObserver:self forKeyPath:@"changeStr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; 3     self.changeStr = @"大家都好"; 4  5  6 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context 7 { 8     NSLog(@"被改變的屬性是%@",keyPath); 9     NSString *str = [change   objectForKey:NSKeyValueChangeNewKey];10     NSString *odlStr = [change   objectForKey:NSKeyValueChangeOldKey];11     NSLog(@"舊屬性是%@",odlStr);12     NSLog(@"新屬性是%@",str);13 }
View Code

 

輸出結果:
 一個Demo: 在LYXItem.h檔案
1 #import <Foundation/Foundation.h>2 3 @interface LYXItem : NSObject4 5 @property(nonatomic, copy) NSString *name;6 @property(nonatomic, copy) NSString *price;7 8 @end

 

在LYXItemView.h檔案

 1 #import <Foundation/Foundation.h> 2 #import "LYXItem.h" 3  4 @interface LYXItemView : NSObject 5  6 @property(nonatomic, weak) LYXItem *item; 7  8 - (void) showItemInfo; 9 10 @end

 

在LYXItemView.m中

 1 #import "LYXItemView.h" 2  3 @implementation LYXItemView 4  5 @synthesize item = _item; 6  7 - (void)showItemInfo 8 { 9     NSLog(@"item名為:%@, 價格為: %@",self.item.name, self.item.price);10 }11 12 13 - (void)setItem:(LYXItem *)item14 {15     self -> _item = item;16     //為item添加監聽器,監聽item的name的屬性的變化17     [self.item addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];18     19     [self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil];20 }21 22 23 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context24 {25     NSLog(@"---------------------------observeValueForKeyPath------------------------");26     NSLog(@"被修改的keyPath為:%@",keyPath);27     NSLog(@"被修改的對象為:%@",object);28     NSLog(@"新被修改的屬性值是:%@",[change objectForKey:@"new"]);29     NSLog(@"被修改的上下文是:%@",context);30 }31 32 33 @end
View Code

 

在運行檔案中

 

 1     LYXItem *item = [[LYXItem alloc] init]; 2     item.name = @"IOS"; 3     item.price = @"6888"; 4      5     LYXItemView *lyxView = [[LYXItemView alloc] init]; 6     lyxView.item = item; 7     [lyxView showItemInfo]; 8      9 //    更改item的值,觸發監聽器的方法10     item.name = @"Android";11     item.price =@"1999";

 

列印結果:

 

          

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.