NSKey​Value​Observing

官方文档点这里:NSKey​Value​ObservingKey-Value Observing Programming Guide

NSKey​Value​Observing (KVO) 非正式协议定义了一个机制,允许一个对象在其他对象的属性发生变化时,收到通知。



Overview


可以观察任意的对象属性,包括 simple attributes, to-one relationships, 和 to-many relationships。attributes 可以理解为标量,比如 int,或者 NSNumber 对象。to-one relationships 可以理解为一个对象,比如一个学生类对象。to-many relationships 可以理解为集合对象,比如 NSArray 对象。观察 to-many relationships 的观察者可以被通知变化的类型和发生变化的对象。

NSObject 实现了 KVO,可以禁止自动通知和手动通知观察者。


Symbols


Change Notification


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

  • 通知观察者发生了变化。
  • key​Path,发生变化的属性。
  • object,发生变化的对象,被观察者。
  • change,包含变化的字典。可用的 key 有 newKey、oldKey、kindKey(比如 insert、replacement,具体看 NSKey​Value​Change 部分) 等,分别获取属性的新值、旧值、变化类型(比如插入、替换)。详情请看 Change Dictionary Keys 部分。
  • context,观察者注册 kvo 时提供的值。
  • 调用 add Observer: for Key Path: options: context: 注册观察者,调用 remove Observer: for Key Path: or remove Observer: for Key Path: context: 移除观察者。当目标发生变化时,观察者就会调用本函数。需要注意的是,调用访问器(getter 和 setter)才会触发 kvo。



Registering for Observation


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

  • 注册观察者,接收 kvo 通知。
  • observer,观察者,必须实现 *observeValueForKeyPath: ofObject: change: context: *方法,收到通知时会调用。
  • key​Path,发生变化的属性。不能为 nil。
  • options,定义 kvo 通知要传递的内容,有 NSKeyValueObservingOptionNew、Old、Initial、Prior。比如 New 意味着通知的 change 字典包含变化之后的值,具体看 NSKey​Value​Observing​Options 部分。
  • context,随意的数据,传给观察者的 observeValueForKeyPath: ofObject: change: context: 方法。
  • 不会 retain 观察者和被观察者(Neither the object receiving this message, nor observer, are retained.)。记得调用 remove Observer: for Key Path: 或 remove Observer: for Key Path: context: 移除观察者。


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

  • 移除对某个属性的观察。
  • 如果移除的对象并没有注册为观察者,会引发异常。
  • 在观察者被 deallocated 之前调用本函数,否则给释放掉的对象发送通知会引发异常。


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

  • 移除对某个属性的观察。
  • 如果移除的对象并没有注册为观察者,会引发异常。
  • 在观察者被 deallocated 之前调用本函数,否则给释放掉的对象发送通知会引发异常。
  • 一个对象可能会对相同的 key path 注册多次,可以根据 context 判断要移除哪一次。



Notifying Observers of Changes


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

  • 属性将要发生变化时通知观察者。用于手动通知观察者。
  • change type 是 NSKey​Value​Change​Setting。
  • 注意,修改属性值之后,要调用 did​Change​Value​For​Key: 。
  • 极少重写本函数,如果重写,记得调用 super 的函数。


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

  • 通知观察者,属性值已经发生变化。用于手动通知观察者。
  • 极少重写本函数,如果重写,记得调用 super 的函数。


- (void)willChange:(NSKeyValueChange)changeKind 
   valuesAtIndexes:(NSIndexSet *)indexes 
            forKey:(NSString *)key;

  • 通知观察者,一个 ordered to-many relationship(比如 NSArray 对象)的 indexes 位置的元素将要发生变化。用于手动通知观察者。
  • change,变化类型,具体看 NSKeyValueChange 部分。
  • indexes, 发生变化的元素下标集合。
  • key,属性名,比如 students。
  • 注意,属性值变化之后,要调用 did​Change:​values​At​Indexes:​for​Key:​ 。
  • 很少重写本函数,如果重写,记得调用 super 的函数。


- (void)didChange:(NSKeyValueChange)changeKind 
  valuesAtIndexes:(NSIndexSet *)indexes 
           forKey:(NSString *)key;

  • 通知观察者位于 indexes 的元素发生了变化。用于手动通知观察者。
  • change,变化类型,具体看 NSKeyValueChange 部分。
  • indexes, 发生变化的元素下标集合。
  • key,属性名,比如 students。
  • 很少重写本函数,如果重写,记得调用 super 的函数。


- (void)willChangeValueForKey:(NSString *)key 
              withSetMutation:(NSKeyValueSetMutationKind)mutationKind 
                 usingObjects:(NSSet *)objects;

  • 通知观察者,一个 unordered to-many relationship(比如 NSSet 对象)将要发生什么类型的变化。用于手动通知。
  • key,属性名,比如 studentSet。
  • mutation​Kind,变化类型,比如交集、并集,具体看 NSKey​Value​Set​Mutation​Kind 部分。
  • objects,参与变化的对象,比如取交集需要两个集合对象。
  • 当发生变化之后,记得调用 did​Change​Value​For​Key:​with​Set​Mutation:​using​Objects: 函数。
  • 重写时记得调用父类方法。


- (void)didChangeValueForKey:(NSString *)key 
             withSetMutation:(NSKeyValueSetMutationKind)mutationKind 
                usingObjects:(NSSet *)objects;

  • 通知观察者,一个 unordered to-many relationship(比如 NSSet 对象)发生了什么类型的变化。用于手动通知。
  • key,属性名,比如 studentSet。
  • mutation​Kind,变化类型,比如交集、并集,具体看 NSKey​Value​Set​Mutation​Kind 部分。
  • objects,参与变化的对象,比如取交集需要两个集合对象。
  • 重写时记得调用父类方法。



Observing Customization


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

  • 返回一个布尔值,表示是否支持自动 kvo。
  • 返回 YES 表示自动调用 will​Change​Value​For​Key:​/did​Change​Value​For​Key:​ 和 will​Change:​values​At​Indexes:​for​Key:​/did​Change:​values​At​Indexes:​for​Key:,或者是遵从 kvc 的方法。
  • 默认返回 YES。


+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key;

  • 返回 key path 的集合对象,其对应的属性值影响参数 key 对应的属性。比如姓名由姓和名决定。
  • 默认实现是查找类似 +key​Paths​For​Values​Affecting<Key> 的方法,如果找到了就返回类似方法的结果集。可以重写该方法,但要调用 super 的方法。具体看 kvo 编程指导,文章顶部有链接。


@property void *observationInfo;


// demo。self 是控制器对象。
- (void)testObservationInfo {
    UIViewController *vc = [UIViewController new];
    [self addObserver:vc forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:NULL];
    id observationInfo = self.observationInfo;
}
  • 一个指针,指向所有观察者的标识信息。
  • The default implementation of this method retrieves the information from a global dictionary of observed objects keyed by memory addresses.
  • For improved performance, both this property and observation​Info can be overridden to store the opaque data pointer in an instance variable. Overrides of this property must not attempt to send messages to the stored data.
  • 可以参考这篇文章:http://www.bkjia.com/IOSjc/993206.html
  • 实际指向一个 NSKeyValueObservationInfo 对象,里面有个数组存放 NSKeyValueObservance 对象。这两个类在文档中并没有找到。NSKeyValueObservance 对象包含了观察者、观察的属性、依赖的属性、注册观察者时的 context 等。如下图所示,观察者是一个控制器对象,被观察的是 view 属性,context 是 NULL。
observationInfo.png




Constants


NSKey​Value​Change
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
  • 表示变化类型。 用于函数 observe​Value​For​Key​Path:​of​Object:​change:​context:​ 的 change 字典的 NSKey​Value​Change​Kind​Key 对应的值。
  • setting 表示被设置了新值。
  • insertion 表示一个 to-many relationship 插入了新的元素。
  • removal 表示一个 to-many relationship 删除了元素。
  • replacement 表示一个 to-many relationship 替换了元素。


NSKey​Value​Observing​Options
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
    NSKeyValueObservingOptionNew = 0x01,
    NSKeyValueObservingOptionOld = 0x02,
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};
  • 用于函数 add​Observer:​for​Key​Path:​options:​context: 的参数​,决定函数 observe​Value​For​Key​Path:​of​Object:​change:​context: 的 change 字典包含哪些键值对。传 0 使得 change 字典不包含其他键值对,除了 NSKey​Value​Change​Kind​Key。
  • new 表示 change 字典包含 NSKeyValueChangeNewKey 键值对,可以获取新值。
  • old 表示 change 字典包含 NSKeyValueChangeOldKey 键值对,可以获取旧值。
  • initial 用于获取初始值,和 new 一起使用。在 add​Observer:​for​Key​Path:​options:​context: 返回之前,就会发送一次通知。对于观察者来说是新值,所以和 new 一起使用,change 字典会包含 NSKeyValueChangeNewKey 键值对,可以获取初始值。
  • prior 表示发送两次通知,在发生变化前后各一次。发生变化前发送的通知的 change 字典里面有个 NSKeyValueChangeNotificationIsPriorKey,对应的值是表示 YES 或者 NO 的 NSNumber 对象,但字典不会包含 NSKeyValueChangeNewKey。发生变化后发送的通知的 change 字典,和不使用 prior 是一样的,也就是没有 NSKeyValueChangeNotificationIsPriorKey 了(When this option is specified the change dictionary in a notification sent after a change contains the same entries that it would contain if this option were not specified )。用于手动通知的时候,有机会调用 -will​ChangeValueForKey: 方法,通知会跟着变化的属性的观察者(比如 firstName 要变化了,想手动通知 fullName 的观察者)。


Change Dictionary Keys
NSKeyValueChangeKey const NSKeyValueChangeKindKey;
NSKeyValueChangeKey const NSKeyValueChangeNewKey;
NSKeyValueChangeKey const NSKeyValueChangeOldKey;
NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey;
  • 用于 observe​Value​For​Key​Path:​of​Object:​change:​context:​ 函数的change 字典。
  • kindKey 表示变化类型,具体看 NSKey​Value​Change 部分。
  • newKey 表示变化后的值。注册观察者时用的 NSKeyValueObservingOptionNew,字典才会有这个键值对。如果 kindKey 是 NSKeyValueChangeInsertion 或者 replacement,newKey 对应的值是一个 NSArray 对象,包含插入或者被替换的元素。
  • oldKey 表示变化前的值。注册观察者时用的 NSKeyValueObservingOptionOld,字典才会有这个键值对。如果 kindKey 是 NSKeyValueChangeRemoval 或者 replacement,newKey 对应的值是一个 NSArray 对象,包含删除或者被替换的元素。
  • indexesKey 表示发生变化的元素下标,值是 NSIndex​Set 对象,如果 kindKey 是 NSKeyValueChangeInsertion、replacement、removal 的话。
  • Notification​Is​Prior​Key 表示是否变化前后发送通知,如果注册观察者时用的 NSKeyValueObservingOptionPrior。


NSKey​Value​Set​Mutation​Kind
typedef NS_ENUM(NSUInteger, NSKeyValueSetMutationKind) {
    NSKeyValueUnionSetMutation = 1,
    NSKeyValueMinusSetMutation = 2,
    NSKeyValueIntersectSetMutation = 3,
    NSKeyValueSetSetMutation = 4
};
  • 用于 will​Change​Value​For​Key:​with​Set​Mutation:​using​Objects:​ 和 did​Change​Value​For​Key:​with​Set​Mutation:​using​Objects: 函数的参数。含义与 NSMutable​Set 的方法 union​Set:​、minus​Set:​、intersect​Set:​ 和 set​Set: 相对应。
  • Union​ 表示发生了并集。change 字典的NSKey​Value​Change​Kind​Key 对应的值是 NSKey​Value​Change​Insertion。
  • Minus 表示两个集合相减,也就是减去两个集合中相同的元素。change 字典的NSKey​Value​Change​Kind​Key 对应的值是 NSKey​Value​Change​Removal。
  • Intersect 表示求交集,删除与另一个集合不相同的元素。change 字典的NSKey​Value​Change​Kind​Key 对应的值是 NSKey​Value​Change​Removal。
  • Set 表示集合的所有元素被另一个集合的元素代替。change 字典的NSKey​Value​Change​Kind​Key 对应的值是 NSKey​Value​Change​Replacement。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容