编码篇-KVO的使用大全

概述

  • KVO,即:Key-Value Observing,它提供一种是基于runtime机制实现的机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
  • KVO是“观察者”设计模式的一种应用,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。
  • 这种模式有利于两个类间的解耦合,尤其是对于业务逻辑与视图控制 这两个功能的解耦合。
  • 和KVC类似,在ObjC中要实现KVO则必须实现NSKeyValueObServing协议,但不用担心,因为NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO.

KVO的内部实现原理:

  • KVO是基于runtime机制实现的。

  • 当某个类的属性对象第一次被观察时,系统就会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。

  • 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person。

  • 我们知道,每一个类中都有一个isa指针指向当前类,所有系统就是在当一个类的对象第一次被观察的时候,系统就会偷偷将isa指针指向动态生成的派生类,从而在被监听属性赋值时被执行的是派生类的setter方法。

  • 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

  • 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类。

KVO的优点:

  • 当 有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。直接可 以在工程里使用,这是KVO 机制提供的最大的优点。其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。

  • 由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么KVO不会有任何开销,因为此时根本就没有KVO代码存在。但是即使没有观察者,委托和NSNotification还是得工作,这也是KVO此处零开销观察的优势。

使用方法

系统框架已经支持KVO,所以程序员在使用的时候非常简单。

  • 1. 注册,指定被观察者的属性。
  • 2. 实现回调方法。
  • 3. 移除观察,对象销毁之前一定要移除观察。

KVO常用的方法

1>注册指定Key路径的监听器

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
相关参数:
observer:观察者,也就是KVO通知的订阅者。订阅着必须实现observeValueForKeyPath:ofObject:change:context:方法
keyPath:描述将要观察的属性,相对于被观察者。 
options:KVO的一些属性配置;有四个选项。 
     options所包括的内容:
     NSKeyValueObservingOptionNew:change字典包括改变后的值 
     NSKeyValueObservingOptionOld:   change字典包括改变前的值 
     NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知 
    NSKeyValueObservingOptionPrior:值改变前是否也要通知(这个key决定了是否在改变前改变后通知两次)
context: 上下文,这个会传递到订阅着的函数中,用来区分消息,所以应当是不同的。

注意:不要忘记解除注册,否则会导致资源泄露。

2>删除指定Key路径的监听器:

- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath
 - (void)removeObserver:(NSObject *)observer     forKeyPath:(NSString *)keyPath  context:(void *)context

3>回调监听

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

  keyPath:被监听的keyPath , 用来区分不同的KVO监听。 
  object: 被观察修改后的对象(可以通过object获得修改后的值) 
  change:保存信息改变的字典(可能有旧的值,新的值等) 
  context:上下文,用来区分不同的KVO监听

  //change 字典中的old new 是关键字,专门用来存储新值和老值
  NSLog(@"oldname %@",[change objectForKey:@"old"]); NSLog(@"new %@",[change objectForKey:@"new"]); } 
  注意,这里(NSString *)keyPath 传过来的就是你添加观察者的时候创建的key ,
  如果想要监听多个属性,你可以根据整个值来判断到底是哪个值的变化触发了该方法

  关于context
  关于 context 参数,其作用可用来标识观察者的身份,在多个观察者观察同一键值时,
  尤其在处理父类和子类都观察同一键值时非常有用。

  那么如何正确声明一个 context 呢? 建议如下:

  static void * XXContext = &XXContext;
  其值就是一个存储自身指针的静态变量值,使用示例如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context == XXContext) {
    
    } else {
          [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

特殊设置

属性依赖

设置类的A属性依赖于B、C属性时,对类的A属性进行观察,当B、C属性发生改变时,也会触发对A的观察者方法。

@interface Person : NSObject
    @property(copy,nonatomic)NSString *name;
    @property(copy,nonatomic)NSString *age;
    @property(copy,nonatomic)NSString *home;
@end

.m中

Paste_Image.png
+ (NSSet *)keyPathsForValuesAffectingImageName
{
    NSSet *set = [NSSet setWithObjects:@"age", @"sex", nil];
    return set;
}

这样设置后通过改变 age、sex,都会触发对 name的观察者方法。

如果值相同不再通知

我们仔细看
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld: change字典包括改变前的值
会发现,上述的设置只是设置了返回的数值是改变前的还是改变后的,但是如果一直设置相同的值,不断重复,还是会不断的触发通知。如何做到值相同不再通知?

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {

  BOOL automatic = NO;
    if ([key isEqualToString:@"age"]) {
      automatic = NO;
  } else {
      automatic = [super automaticallyNotifiesObserversForKey:key];
  }
  return automatic;
}
- (void)setAge:(NSString *)age
{
  if (self.age != age) {
      //发送通知:键值即将改变
      [self willChangeValueForKey:@"age"];
      _age = age;
      //发送通知:键值已经修改
      [self didChangeValueForKey:@"age"];
  }
}

这样设置后就可达到有新值通知,无新值不通知的效果。

- (void)viewDidLoad {
        [super viewDidLoad];
    
    Person *person = [[Person alloc]init];
    [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:@"person"];
    person.age = @"1111";
    person.age = @"1111";
    person.age = @"2222";
    person.age = @"2222";
    
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"+++ %@",[change objectForKey:@"new"]);
}

补充说明:

*******************************************
/*如果在这里设置了手动通知的话,就必须实现 下面这俩方法,在 set方法中,或者在 其他地方,
  否则正常的 set方法后不会触发通知。
  [self willChangeValueForKey:@"age"];
   [self didChangeValueForKey:@"age"];
 
  在ViewDidLoad中,如果person中实现了automaticallyNotifiesObserversForKey,
  没有重写  set方法的话,下面的代码,“222”的不会触发观察方法, “DJ Earworm”会触发观察方法。

  person.age = @"2222";
  [person willChangeValueForKey:@"age"];
  person.age = @"DJ Earworm";
  [person didChangeValueForKey:@"age"];

注意: KVO只能监听通过set方法修改的值

 p.age = 100; // 调用了set方法
 p->_age = 998; // 不会监听到,因为KVO只监听通过set方法修改的属性值,
  而p->age并未不是通过set方法修改属性值的,这种方式是通过修改全局变量 age,再把age赋值给 _age,最后达到修改属性值的效果。

****************************************************************

 @interface Person : NSObject
  {
    @public    //一定要写,不然会报错。
      NSString *newage;
  }
 @property(copy,nonatomic)NSString *age;

@implementation Person
@synthesize age = newage;

   Person *person = [[Person alloc]init];
   person ->newage = @"nima";
   NSLog(@"%@",person.age);  zhey 

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

推荐阅读更多精彩内容

  • 本文结构如下: Why? (为什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等开会阅读 1,649评论 1 21
  • 本文由我们团队的 纠结伦 童鞋撰写。 文章结构如下: Why? (为什么要用KVO) What? (KVO是什么...
    知识小集阅读 7,412评论 7 105
  • 上半年有段时间做了一个项目,项目中聊天界面用到了音频播放,涉及到进度条,当时做android时候处理的不太好,由于...
    DaZenD阅读 3,017评论 0 26
  • 1.Unity 5.6 的光照模式 http://gad.qq.com/program/translateview...
    GameMobile阅读 194评论 0 0
  • 已经是身边越来越多的好友开始谈婚论嫁的年纪,从最初不能想象到如今视若平常,除了自己哪天也加入到这个日益庞大的队伍,...
    良荩阅读 178评论 0 0