iOS中的 觀察者模式 之 KVO


1、KVO的簡介

     KVO 全稱 Key-Value Observing。中文叫鍵值觀察。KVO其實是一種觀察者模式,觀察者在鍵值改變時會得到通知,利用它可以很容易實現視圖組件和數據模型的分離,當數據模型的屬性值改變之后作為監聽器的視圖組件就會被激發,激發時就會回調監聽器自身。相比Notification,KVO更加的簡單直接。

     KVO的操作方法由NSKeyValueCoding提供,而他是NSObject的類別,也就是說ObjC中幾乎所有的對象都支持KVO操作。

 

   KVO 需要實現實例變量的 setter/getter方法

2、KVO的實現

 

    0) KVO的使用也很簡單,就是簡單的3步。

 

  (1)注冊需要觀察的對象的屬性addObserver:forKeyPath:options:context:

 

  (2)實現observeValueForKeyPath:ofObject:change:context:方法,這個方法當觀察的屬性變化時會自動調用.在這個方法中還通過                 NSKeyValueObservingOptionNew這個參數要求把新值在dictionary中傳遞過來。

 

  (3)取消注冊觀察removeObserver:forKeyPath:context:

 

     1)注冊

     實現方法:

    - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable     void *)context;

     方法參數:

     object : 被觀察對象

       observer: 觀察對象

       forKeyPath里面帶上property的name,如UIView的frame、center等等

       options: 有4個值,分別是:

       NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法

       NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法

       NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。

       NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后。

       注:例子里的0就代表不帶任何參數進去

       context: 可以帶入一些參數,其實這個挺好用的,任何類型都可以,自己強轉就好了。

  2)監測 

   實現方法:

     - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id>          *)change context:(nullable void *)context;

     這個方法當觀察的屬性變化時會自動調用.在這個方法中還通過NSKeyValueObservingOptionNew這個參數要求把新值在dictionary中傳遞過來。

  方法參數:

       keyPath: 對應forKeyPath

       object:  被觀察的對象

       change:  對應options里的NSKeyValueObservingOptionNew、NSKeyValueObservingOptionOld等

       context: 對應context

  3)取消注冊觀察

     實現方法:

  - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);

   方法參數:

  同上

3、具體實現代碼

  main.m

 1 //
 2 //  main.m
 3 //  KVO具體實現
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "Person.h"
11 #import "Watch.h"
12 
13 int main(int argc, const char * argv[]) {
14     @autoreleasepool {
15         
16         Person *person = [[Person alloc] init];
17         
18         Watch *watch = [[Watch alloc] init];
19         
20         person.watch = watch;
21         
22         [person registerObserver];
23         
24         person.name = @"pss";
25         
26         person.name = @"pbb";
27         
28     }
29     return 0;
30 }
main.m

  person.h

 1 //
 2 //  Person.h
 3 //  KVO具體實現
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import <Foundation/Foundation.h>
10 #import "Watch.h"
11 
12 @interface Person : NSObject
13 
14 @property (nonatomic, copy) NSString *name;
15 
16 @property (nonatomic, strong) Watch *watch;
17 
18 - (void)registerObserver;
19 
20 @end
Person.h

  Person.m

 1 //
 2 //  Person.m
 3 //  KVO具體實現
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import "Person.h"
10 
11 @implementation Person
12 
13 - (void)dealloc {
14     
15     [self removeObserver:self.watch forKeyPath:@"name"];
16 }
17 
18 - (void)registerObserver {
19     
20     [self addObserver:self.watch forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
21 }
22 @end
Person.m

  Watch.m

 1 //
 2 //  Watch.m
 3 //  KVO具體實現
 4 //
 5 //  Created by ma c on 16/5/18.
 6 //  Copyright © 2016年 彭盛凇. All rights reserved.
 7 //
 8 
 9 #import "Watch.h"
10 
11 @implementation Watch
12 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
13     
14     NSLog(@"new = %@",[change valueForKey:@"new"]);
15     
16     NSLog(@"old = %@",[change valueForKey:@"old"]);
17     
18     NSLog(@"---------------------------");
19 }
20 @end
Watch.h

  Log打印日志

 

4、KVO常見崩潰錯誤與解決方式

  1)沒有在准確位置實現dealloc , 注冊,刪除,監聽 操作方法

  2)沒有實現dealloc中的remove操作

 

 5、手動實現KVO

 1 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
 2     
 3     BOOL automatic = YES;
 4     if ([key isEqualToString:@"age"]) {
 5         automatic = NO;
 6     } else {
 7         automatic = [super automaticallyNotifiesObserversForKey:key];
 8     }
 9     
10     return automatic;
11 }
12 
13 - (void)setAge:(int)age {
14     
15     //手動設置KVO
16     
17     if (_age != age) {
18         
19         [self willChangeValueForKey:@"age"];
20 
21         _age = age;
22         
23         [self didChangeValueForKey:@"age"];
24         
25     }
26 }

6、KVO的實現原理

當某一個類的實例第一次使用KVO的時候,系統就會在運行期間動態的創建該類的一個派生類,該類的命名規則一般是以NSKVONotifying為前綴,以原本的類名為后綴。並且將原型的對象的isa指針指向該派生類。同時在派生類中重載了使用KVO的屬性的setter方法,在重載的setter方法中實現真正的通知機制,正如前面我們手動實現KVO一樣。這么做是基於設置屬性會調用 setter 方法,而通過重寫就獲得了 KVO 需要的通知機制。當然前提是要通過遵循 KVO 的屬性設置方式來變更屬性值,如果僅是直接修改屬性對應的成員變量,是無法實現 KVO 的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM