iOS 观察者KVO

KVO 的基本概念(Key Value Observing)
  • 基本概念
      键值观察是一种使对象获取其他对象的特定属性变化的通知机制。控制器层的绑定技术就是严重依赖键值观察获得模型层和控制器层的变化通知的。对于不依赖控制器层类的应用程序,键值观察提供了一种简化的方法来实现检查器并更新用户界面值。
      与NSNotification不同,键值观察中并没有所谓的中心对象来为所有观察者提供变化通知。取而代之地,当有变化发生时,通知被直接发送至处于观察状态的对象。NSObject提供这种基础的键值观察实现方法,你几乎不用重写该方法。
      你可以观察任意对象属性,包括简单属性,对一或对多关系。对多关系的观察者将会被告知发生变化的类型,也就是任意发生变化的对象。
      键值观察为所以对象提供自动观察兼容性。你可以通过禁用自动观察通知并实现手动通知来筛选通知。

  • 注册观察者
      为了正确接收属性的变更通知,观察对象必须首先发送一个addObserver: forKeyPath: options: context: 消息至被观察对象,用以传送观察对象和需要观察的属性的关键路径,以便于其注册。选项参数指定了发送变更通知时提供给观察者的信息。使用NSKeyValueObservingOptionOld选项可以将初始对象值以变更字典中的一个项的形式提供给观察者。指定NSKeyValueObservingOptionNew选项可以将新的值以一个项的形式添加至变更字典。你可以用逐为"|"同时使用这两个常量来指定上述两种类型的值。

person.name = @"xiao wang";改变姓名 person.name = @"xiao ming";
  /*
     作用:给对象绑定一个观察者(监听者)
     addObserver:   观察者
     forKeyPath:    要监听的属性
     options:       选项(方法中拿到属性值)
     context:       上下文一般为nil
   */
  //监听变更旧的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];
此时打印:
change = {
  kind = 1;
  old = "xiao wang";
}
  //监听变更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
此时打印:
change = {
  kind = 1;
  new = "xiao ming";
}
  //同时监听变更旧的值和新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
此时打印:
change = {
  kind = 1;
  new = "xiao ming";
  old = "xiao wang";
}
  • 接收变更通知
      当监听的属性发生变动时,观察者收到observeValueForKeyPath: ofObject: change: context: 消息,观察者必须实现这一方法。触发观察者通知的对象和键路径、包含变更细节的字典,以及观察者注册时提交的上下文指针均被提交给观察者
/**
*  当监听的属性值发生改变是执行
*
*  @param keyPath 发生改变的属性
*  @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";
}

  • 移除观察者身份
      你可以发送一条指定指定观察者对象和键路径的removeObserver: forKeyPath: 消息至被观察的对象,来移除一个键值观察者。
      //移除观察者身份
      [person removeObserver:self forKeyPath:@"name"];
    
    
KVO 使用需要注意的一些地方
  • 在对象销毁时要先移除观察者身份,否则会报错
- (void)viewDidLoad {
  [super viewDidLoad];
  
  Person *person = [[Person alloc] init];
  
  person.name = @"xiao wang";
  
  //监听变更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  
  person.name = @"xiao ming";
}
/**
*  当监听的属性值发生改变是执行
*
*  @param keyPath 发生改变的属性
*  @param object  改变的属性所属的对象
*  @param change  改变的内容
*  @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
  
  // 打印改变的内容
  NSLog(@"change = %@",change);
}
  
在执行完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   图片处理                        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  图片处理                        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";
  
  //监听变更新的值
  [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  
  person.name = @"xiao ming";
  
  //移除观察者身份
  [person removeObserver:self forKeyPath:@"name"];

}

  • 修改观察的属性的值需要用“.”语法去修改或者KVC,改下划线属性(__examTime),KVO并不能监听到
    创建一个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 方法中监听
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是可以监听的到时间变化的
   */
    
  //_examTime--;
  /*
   在ViewController 的viewDidLoad 方法中监听
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是不能监听的到时间变化的
   */
    
    
   //[self setValue:[NSNumber numberWithInt:_examTime] forKey:@"examTime"];
  /*
   在ViewController 的viewDidLoad 方法中监听
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
     
   是可以监听的到时间变化的
   */
    
    
   //[self setValue:[NSNumber numberWithInt:_examTime] forKey:@"_examTime"];
  /*
   在ViewController 的viewDidLoad 方法中监听
   Student *student = [[Student alloc] init];
     
   [student addObserver:self forKeyPath:@"examTime" options:NSKeyValueObservingOptionOld context:nil];
   
   是不能监听的到时间变化的原因是KVO注册监听的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--不能触发观察者模式
   固不作进一步优化
   */
}
  
/**
*  当监听的属性值发生改变是执行
*
*  @param keyPath 发生改变的属性
*  @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;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容