关于KVO的那些你不知道的细节

首先:demo在这里。里面有你想要的一切。

KVODemo

kvo的使用方法: 这个简单,你懂就不用看使用方法了。
第一步: 创建对象:拿到对象的属性,如果你想监听哪一个属性,然后卸载keyPath里面
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;


第二步: 实现这个方法,拿到chage里面值就ok了
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

第三步: 完了就移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
按照规矩,还是先来底层实现原理走一波(了解的就不用看了,重点在下面)

KVO是基于runtime机制实现的
1、当某个类的属性对象第一次被观察时,系统就会在运行期间动态地创建该类的一个派生类,在这个派生类中重写基类的任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制
2、如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
3、每一个类中都有一个isa指针指向当前类,所有系统就是在当一个类的对象第一次被观察的时候,系统就会将isa指针指向动态生成的派生类,从而在被监听属性赋值时被执行的是派生类的setter方法。
4、键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,因此 observeValueForKey:ofObject:change:context: 也会被调用。
5、KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类

(你要是看不懂上面这些是说的啥,我记得的demo里面有自定义的kvo,不是太全面。但是能懂原理.....)

重点来了,都在这里了。

首先分析一下: 下面这个方法中的option中的四个枚举值。

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

KVO 的 option属性的枚举值的作用。
NSKeyValueObservingOptionNew 返回新值的时候发通知
NSKeyValueObservingOptionOld 返回旧值的时候发通知
NSKeyValueObservingOptionInitial 在注册的时候就会发一次通知,在改变的时候也会发通知
NSKeyValueObservingOptionPrior 改变之前发一次,改变之后发一次

一般情况下我们基本上会用 NSKeyValueObservingOptionNew这个。知道了这个东西之后不知道你有没有想过用一个不就够了么,特么的还给那么多。其实苹果大大还有好多东西你不太清除呢....提示下:键值观察的两个方法
-(void)willChangeValueForKey:(NSString *)key;
-(void)didChangeValueForKey:(NSString *)key;

你或许会想。这两个方法谁不知道呀.....但是重点在于KVO的消息发送的模式用两种:一种是自动的,一种是手动的。
什么意思呢?
解释一波:在一般情况下我们在使用kvo的时候只是想到某一个东西条件改变的时候我们要知道他的状态,但是还有一些情况就是如果满足一定条件后你需要通知,在不满足条件的时候就不需要因此苹果给我们了这两种模式,解决了这中尴尬的局面。

所谓的自动通知:
就是你在观察某一个类的时候。在这个被观察的类中有一个方法叫做 (说白了就是NSObject的方法)

 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

他的默认返回值就是YES,意思就是如果你不重写这个方法的话,你观察的所有的类的某个值都是自动通知。

手动通知:
就是你手动重写了这个方法,将方法的返回值设置成了NO。这个时候你在运行发现KVO已经不能使用了,原因就是你改变了他发送通知的机制,导致在发送通知的过程中他被阻塞了。如果想让他继续执行下去你需要手动的去实现两个方法:

        - (void)willChangeValueForKey:(NSString *)key;
        - (void)didChangeValueForKey:(NSString *)key;

但是需要知道调用顺序:首先说一下 willChangeValueForKey 这个方法就是在观察某个属性之前的变化
didChangeValueForKey 这个方法是观察的某个属性改变之后的方法,注意这两个一定要成双成对(因为KVO的option中的四个key值的关系,因此需要成双成对的写)
他们的顺序:

         [_p1 willChangeValueForKey:@"name"];
         _p1.name = @"hello";
         [_p1 didChangeValueForKey:@"name"];
重点之二: 这个方法中的 keyPath 的使用
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

为啥是keyPath呀,不就是一个键值路径么。如果你观察的类中又有一个类。但是你想观察当前观察的类中的那个类的某一个属性的变化。(我就说点大白话:如果你观察的类是Person,person中又有一个Dog类,你想观察的是Person中的Dog类中的某一个属性)

这个时候keyPath就用到喽:使用看代码

// 我的Person类中有一个Dog类,你可以看源码...
    Person *p1 = [Person new];
    _p1 = p1;
    [p1 addObserver:self forKeyPath:@"dog.age" options:NSKeyValueObservingOptionNew context:nil];

/// 这是观察这个类中的某一个类的属性(通过keyPath使用)
- (void)demo2{
    _p1.dog.age = 10;
}

重点使用之三:观察数组的变化

首先你得知道kvo是观察是通过set方法搞的。但是数组变化的时候是调用的比如:
-(void)addObject:(ObjectType)anObject;这个方法不是set方法,因此你发现你的kvo不打印。怎么办?看代码:

他需要一些特定的处理方法: 使用 kvc方式。
-(void)mutableArrayValueForKey:这个方法,转换成一个临时数组。然后在进行addObject: 添加就ok了

   Person *p2 = [Person new];
   _p2 = p2;
   [p2 addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew context:nil];

/// 这个是观察数组的变化
- (void)demo3{
    NSMutableArray *tempArr = [_p2 mutableArrayValueForKey:@"arr"];
    [tempArr addObject:@"object"];
}

重点使用之四:观察某一个类中的另外一个类的所有的属性
    ///4、键值观察一个对象的多个属性的值的变化的(观察Dog类中的所有的属性的变化)
    /*
     如果我们直接使用就不会走observe的方法,我们需要在Person类中进行一些特殊的处理。
    重写 keyPathsForValuesAffectingValueForKey这个方法。他的返回值一个NSSet集合
     */
    Person *p3 = [Person new];
    _p3 = p3;
    [p3 addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];


/// 这个是观察摸一个类中的另外一个类的所有的属性
- (void)demo4{
    _p3.dog.age = 10;
    _p3.dog.level = 20;
}
#import "Person.h"
@implementation Person
- (instancetype)init
{
    self = [super init];
    if (self) {
        _dog = [[Dog alloc] init];
        _dog.age = 2;
        _dog.level = 3;
        _arr = [NSMutableArray array];
    }
    return self;
}
/*
    返回的是一个NSSet集合,这个key 就是外部观察的这个类中的哪一个变量(即:dog)
 */
+(NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
//    NSLog(@"%@", key);
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"dog"]){
        NSArray * arr = @[@"_dog.age", @"_dog.level"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:arr];
    }
    return keyPaths;
}
@end

点击有代码

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

推荐阅读更多精彩内容