iOS 觀察者KVO

KVO 的基本概念(Key Value Observing)
  • 基本概念
      鍵值觀察是一種使對象獲取其他對象的特定屬性變化的通知機制昵观。控制器層的綁定技術就是嚴重依賴鍵值觀察獲得模型層和控制器層的變化通知的舌稀。對于不依賴控制器層類的應用程序啊犬,鍵值觀察提供了一種簡化的方法來實現(xiàn)檢查器并更新用戶界面值壁查。
      與NSNotification不同觉至,鍵值觀察中并沒有所謂的中心對象來為所有觀察者提供變化通知。取而代之地睡腿,當有變化發(fā)生時语御,通知被直接發(fā)送至處于觀察狀態(tài)的對象。NSObject提供這種基礎的鍵值觀察實現(xiàn)方法席怪,你幾乎不用重寫該方法应闯。
      你可以觀察任意對象屬性,包括簡單屬性挂捻,對一或對多關系碉纺。對多關系的觀察者將會被告知發(fā)生變化的類型,也就是任意發(fā)生變化的對象刻撒。
      鍵值觀察為所以對象提供自動觀察兼容性骨田。你可以通過禁用自動觀察通知并實現(xiàn)手動通知來篩選通知。

  • 注冊觀察者
      為了正確接收屬性的變更通知声怔,觀察對象必須首先發(fā)送一個addObserver: forKeyPath: options: context: 消息至被觀察對象态贤,用以傳送觀察對象和需要觀察的屬性的關鍵路徑,以便于其注冊醋火。選項參數(shù)指定了發(fā)送變更通知時提供給觀察者的信息悠汽。使用NSKeyValueObservingOptionOld選項可以將初始對象值以變更字典中的一個項的形式提供給觀察者箱吕。指定NSKeyValueObservingOptionNew選項可以將新的值以一個項的形式添加至變更字典。你可以用逐為"|"同時使用這兩個常量來指定上述兩種類型的值柿冲。

person.name = @"xiao wang";改變姓名 person.name = @"xiao ming";
  /*
     作用:給對象綁定一個觀察者(監(jiān)聽者)
     addObserver:   觀察者
     forKeyPath:    要監(jiān)聽的屬性
     options:       選項(方法中拿到屬性值)
     context:       上下文一般為nil
   */
  //監(jiān)聽變更舊的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
此時打又呈稀:
change = {
  kind = 1;
  old = "xiao wang";
}
  //監(jiān)聽變更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
此時打印:
change = {
  kind = 1;
  new = "xiao ming";
}
  //同時監(jiān)聽變更舊的值和新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
此時打印:
change = {
  kind = 1;
  new = "xiao ming";
  old = "xiao wang";
}
  • 接收變更通知
      當監(jiān)聽的屬性發(fā)生變動時,觀察者收到observeValueForKeyPath: ofObject: change: context: 消息姻采,觀察者必須實現(xiàn)這一方法。觸發(fā)觀察者通知的對象和鍵路徑爵憎、包含變更細節(jié)的字典慨亲,以及觀察者注冊時提交的上下文指針均被提交給觀察者
/**
*  當監(jiān)聽的屬性值發(fā)生改變是執(zhí)行
*
*  @param keyPath 發(fā)生改變的屬性
*  @param object  改變的屬性所屬的對象
*  @param change  改變的內容
*  @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
  
  // 打印改變的內容
  NSLog(@"change = %@",change);
}
打印結果如:
change = {
  kind = 1;
  new = "xiao ming";
  old = "xiao wang";
}

  • 移除觀察者身份
      你可以發(fā)送一條指定指定觀察者對象和鍵路徑的removeObserver: forKeyPath: 消息至被觀察的對象,來移除一個鍵值觀察者宝鼓。
      //移除觀察者身份
      [person removeObserver:self forKeyPath:@"name"];
    
    
KVO 使用需要注意的一些地方
  • 在對象銷毀時要先移除觀察者身份刑棵,否則會報錯
- (void)viewDidLoad {
  [super viewDidLoad];
  
  Person *person = [[Person alloc] init];
  
  person.name = @"xiao wang";
  
  //監(jiān)聽變更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  
  person.name = @"xiao ming";
}
/**
*  當監(jiān)聽的屬性值發(fā)生改變是執(zhí)行
*
*  @param keyPath 發(fā)生改變的屬性
*  @param object  改變的屬性所屬的對象
*  @param change  改變的內容
*  @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
  
  // 打印改變的內容
  NSLog(@"change = %@",change);
}
  
在執(zhí)行完viewDidLoad方法后,person會被銷毀愚铡。然而viewDidLoad在person不需要再使用的時候并沒有移除person的觀察者身份會引起crash
change = {
  kind = 1;
  new = "xiao ming";
}
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7fa633d97950 of class Person was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x7fa633d920d0> (
<NSKeyValueObservance 0x7fa633d9ad80: Observer: 0x7fa633e13090, Key path: name, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x7fa633d9ac40>
)'
*** First throw call stack:
(
  0   CoreFoundation                      0x000000010ca92e65 __exceptionPreprocess + 165
  1   libobjc.A.dylib                     0x000000010c50bdeb objc_exception_throw + 48
  2   CoreFoundation                      0x000000010ca92d9d +[NSException raise:format:] + 205
  3   Foundation                          0x000000010c11d611 NSKVODeallocate + 294
  4   libobjc.A.dylib                     0x000000010c51fafe _ZN11objc_object17sidetable_releaseEb + 232
  5   ???áaá?§?áêü                        0x000000010c0089bc -[ViewController viewDidLoad] + 252
  6   UIKit                               0x000000010cfd5f98 -[UIViewController loadViewIfRequired] + 1198
  7   UIKit                               0x000000010cfd62e7 -[UIViewController view] + 27
  8   UIKit                               0x000000010ceacab0 -[UIWindow addRootViewControllerViewIfPossible] + 61
  9   UIKit                               0x000000010cead199 -[UIWindow _setHidden:forced:] + 282
  10  UIKit                               0x000000010cebec2e -[UIWindow makeKeyAndVisible] + 42
  11  UIKit                               0x000000010ce37663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
  12  UIKit                               0x000000010ce3dcc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760
  13  UIKit                               0x000000010ce3ae7b -[UIApplication workspaceDidEndTransaction:] + 188
  14  FrontBoardServices                  0x000000010f80b754 -[FBSSerialQueue _performNext] + 192
  15  FrontBoardServices                  0x000000010f80bac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
  16  CoreFoundation                      0x000000010c9bea31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
  17  CoreFoundation                      0x000000010c9b495c __CFRunLoopDoSources0 + 556
  18  CoreFoundation                      0x000000010c9b3e13 __CFRunLoopRun + 867
  19  CoreFoundation                      0x000000010c9b3828 CFRunLoopRunSpecific + 488
  20  UIKit                               0x000000010ce3a7cd -[UIApplication _run] + 402
  21  UIKit                               0x000000010ce3f610 UIApplicationMain + 171
  22  ???áaá?§?áêü                        0x000000010c00a51f main + 111
  23  libdyld.dylib                       0x000000010f1ce92d start + 1
  24  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
  
修復viewDidLoad:
- (void)viewDidLoad {
  [super viewDidLoad];
  
  Person *person = [[Person alloc] init];
  
  person.name = @"xiao wang";
  
  //監(jiān)聽變更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  
  person.name = @"xiao ming";
  
  //移除觀察者身份
  [person removeObserver:self forKeyPath:@"name"];

}

  • 修改觀察的屬性的值需要用“.”語法去修改或者KVC蛉签,改下劃線屬性(__examTime),KVO并不能監(jiān)聽到
    創(chuàng)建一個Student類
    Student.h文件
#import <Foundation/Foundation.h>
    
@interface Student : NSObject
    
// 離考試時間
@property (nonatomic, assign) int examTime;
    
@end

Student.m文件

#import "Student.h"
  
@implementation Student
  
/**
*  初始化方法
*
*  @return <#return value description#>
*/
- (instancetype)init{
    
  self = [super init];
    
  if (self != nil) {
        
      self.examTime = 10;
        
      //設置一個定時器沥寥,減少離考試的時間examTime
      [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeGone:) userInfo:nil repeats:YES];
        
  };
    
  return self;
}
  
//定時器方法
- (void)timeGone:(NSTimer *)timer{
    
  self.examTime--;
  /*
   self.examTime--;
   在ViewController 的viewDidLoad 方法中監(jiān)聽
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是可以監(jiān)聽的到時間變化的
   */
    
  //_examTime--;
  /*
   在ViewController 的viewDidLoad 方法中監(jiān)聽
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是不能監(jiān)聽的到時間變化的
   */
    
    
   //[self setValue:[NSNumber numberWithInt:_examTime] forKey:@"examTime"];
  /*
   在ViewController 的viewDidLoad 方法中監(jiān)聽
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是可以監(jiān)聽的到時間變化的
   */
    
    
   //[self setValue:[NSNumber numberWithInt:_examTime] forKey:@"_examTime"];
  /*
   在ViewController 的viewDidLoad 方法中監(jiān)聽
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
   
   是不能監(jiān)聽的到時間變化的原因是KVO注冊監(jiān)聽的key是@"examTime", KCV改變的key是@"_examTime"
   */
  
}
  
@end
    
在ViewController的viewDidLoad方法中使用
- (void)viewDidLoad {
  [super viewDidLoad];
    
  Student *student = [[Student alloc] init];
    
  [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
    
  /*
   這里的代碼存在問題
   沒有添加移除student觀察者身份碍舍,但此處只是為了驗證_examTime--不能觸發(fā)觀察者模式
   固不作進一步優(yōu)化
   */
}
  
/**
*  當監(jiān)聽的屬性值發(fā)生改變是執(zhí)行
*
*  @param keyPath 發(fā)生改變的屬性
*  @param object  改變的屬性所屬的對象
*  @param change  改變的內容
*  @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
  // 打印改變的內容
  NSLog(@"change = %@",change);
    
}
  
打印結果:
change = {
  kind = 1;
  old = 10;
}
change = {
  kind = 1;
  old = 9;
}
change = {
  kind = 1;
  old = 8;
}
change = {
  kind = 1;
  old = 7;
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市邑雅,隨后出現(xiàn)的幾起案子片橡,更是在濱河造成了極大的恐慌,老刑警劉巖淮野,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捧书,死亡現(xiàn)場離奇詭異,居然都是意外死亡骤星,警方通過查閱死者的電腦和手機经瓷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洞难,“玉大人舆吮,你說我怎么就攤上這事±扔” “怎么了歪泳?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長露筒。 經(jīng)常有香客問我呐伞,道長,這世上最難降的妖魔是什么慎式? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任伶氢,我火速辦了婚禮趟径,結果婚禮上,老公的妹妹穿的比我還像新娘癣防。我一直安慰自己蜗巧,他們只是感情好,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布蕾盯。 她就那樣靜靜地躺著幕屹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪级遭。 梳的紋絲不亂的頭發(fā)上望拖,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音挫鸽,去河邊找鬼说敏。 笑死,一個胖子當著我的面吹牛丢郊,可吹牛的內容都是我干的盔沫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼枫匾,長吁一口氣:“原來是場噩夢啊……” “哼架诞!你這毒婦竟也來了?” 一聲冷哼從身側響起婿牍,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤侈贷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后等脂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俏蛮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年上遥,在試婚紗的時候發(fā)現(xiàn)自己被綠了搏屑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡粉楚,死狀恐怖辣恋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情模软,我是刑警寧澤伟骨,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站燃异,受9級特大地震影響携狭,放射性物質發(fā)生泄漏。R本人自食惡果不足惜回俐,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一逛腿、第九天 我趴在偏房一處隱蔽的房頂上張望稀并。 院中可真熱鬧,春花似錦单默、人聲如沸碘举。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽引颈。三九已至,卻和暖如春境蜕,著一層夾襖步出監(jiān)牢的瞬間线欲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工汽摹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苦锨。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓逼泣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親舟舒。 傳聞我的和親對象是個殘疾皇子拉庶,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容