浅谈kvo
来源:互联网 发布:通过itunes安装软件 编辑:程序博客网 时间:2024/06/11 09:56
关于kvo即Key-Value Observing ,下面记下读官方文档https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html的个人总结,一方面希望能够加深对kvo的了解的,另一方面能够和大家一起讨论,如有错误,请留言指正,万分感谢。
一、什么是kvo
kvo是一个对象用来观察另一个对象(可以是自己)属性的变化,并作出处理的一种机制。当被观察对象的属性变化时,系统会发出一个通知,来通知观察对象。kvo实现机制的前提是对象遵守kvc非正式协议,所有继承于NSObject的对象,并且按一般命名规则定义属性的,都遵守kvc机制。
假设有一个Person对象,他有一个Account属性。他需要当账户发生变动时接收到一个通知,首先account要注册person为其观察者addObserver:forKeyPath: options:context:,person对象为了收到通知,需实现observeValueForKeyPath:ofObject:change:context:方法,如果不想监听账户的变化了,那么需要在person 对象dealloc调用之前,调用removeObserver:forKeyPath:
移除对账户特定属性的监听.
Options
addObserver:forKeyPath: options:context:,参数options 传入按位或运算的常量,不但影响到通知的内容,还影响到通知产生的方式,通过N
SKeyValueObservingOptionOld
.来获取属性变化之前的值,通过NSKeyValueObservingOptionNew获取属性变化之后的值,通过或运算 | 来同时获取变化之前和变化之后的值即传入NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew。也可传入NSKeyValueObservingOptionInitial来让被观察对象在addObserver:forKeyPath: options:context:return之前发出一个一次通知。 可以用来在observeValueForKeyPath:ofObject:change:context:初始化观察者对象属性的值。options的值传入NSKeyValueObservingOptionPrior可以在被观察对象属性的值发生变化之前,由被观察对象发出一个通知.
Context 可以包含任意的数据,并被传参到OberserveValueForKeyPath:ofObject:change:context:方法中,你可以传入NULL,但是当观察对象的父类对象同时对该KeyPath注册了观察者的时候就会产生问题,因此安全的做法是可以使用context来区分观察者是子类还是父类。你可以为所有的类定义一个context,而用keyPath来区分观察的属性,也可以为所有要观察的对象的属性来定义不同的context,然后通过不同的context来区分观察的属性。记住在找不到对应的属性时一定要调用【super observeValueForKeyPath:ofObject:change:context:]方法,因为有可能是父类的监听
举例:
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
- (void)registerAsObserverForAccount:(Account*)account {
[account addObserver:self
forKeyPath:@"balance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountBalanceContext];
[account addObserver:self
forKeyPath:@"interestRate"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:PersonAccountInterestRateContext];
}
需要注意的是这个方法中,对于观察者,被观察者,context都是弱引用。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == PersonAccountBalanceContext) {
// Do something with the balance…
} else if (context == PersonAccountInterestRateContext) {
// Do something with the interest rate…
} else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
change Dictionary key NSKeyValueChangeKindKey包含了变化的类型,如果被观察对象的值发生了变化,那么对NSKeyValueChangeKindKey取值会获得
NSKeyValueChangeSetting
.值 依赖于注册观察者时options传入的参数,NSKeyValueChangeOldKey
和NSKeyValueChangeNewKey,分别返回变化之前的值和改变之后的值,如果被观察的对象属性是一个对象,那么直接返回该对象的值,如果是标量或者是c的结构体,那么返回NSNumber对象或者,NSValue对象。
如果被观察的对象的属性是一个to-many 关系,那么NSKeyValueChangeKindKey通过返回
NSKeyValueChangeInsertion
,NSKeyValueChangeRemoval
, orNSKeyValueChangeReplacement来表示这个对象属性所做改变的方式是插入,删除,或者是替换,而NSKeyValueChangeIndexesKey则返回一个包含所改变的索引的NSSet对象,NSKyeValueCHangeOldKey和NSKeyValueChangeNewKey则返回改变之前的集合对象和该变之后的集合对象。
当你不需要监听被观察对象属性变化的时候一定要记得调用 removeObserver: forKeyPath: context方法,并且在调用这个方法之前,一定要确认 已经注册了观察者,否者会抛出NSRangeException异常,当然可以在try-catch block里来调用捕获异常。观察对象并不会自动移除观察模式,即使它已经被dealloc掉了,因此它会继续接受被观察者对象发出的通知,而对一个被销毁的对象接受通知会导致内存异常。协议并没有提供是否是观察者或者是被观察者的方法,因此需要合理的组织代码,一般在初始化或者viewDidLoad里注册观察者,在dealloc里移除观察者。
- (void)unregisterAsObserverForAccount:(Account*)account {
[account removeObserver:self
forKeyPath:@"balance"
context:PersonAccountBalanceContext];
[account removeObserver:self
forKeyPath:@"interestRate"
context:PersonAccountInterestRateContext];
}
二、如何手动控制kvo的过程
那么什么样的类的属性是遵守kvo的呢?1、首先这个类的属性必须是遵守kvc的2这个类为这个属性的变化发送kvo通知3、所依赖的key要被正确注册
什么情况下改变属性会发送kvo通知呢?1.使用通用的set 方法,2.使用kvc中的setValueForKey:和setValueForKeyPath:都会使类自动发送kvo通知。
手动发送kvo通知:在有些情况下,你想控制发送通知的过程,来减少不必要的通知,或者是将一组数据的变化通知,改成一个通知。想控制通知的发送过程需要重写automaticallyNotifiesObserversForKey:类方法。对于手动管理的通知的属性返回NO, 对于不手动控制通知过程的属性,用super调用该方法。举例如下:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
手动控制通知的发送需要调用willChangeValueForKey:和didChangeValueForKey:方法
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}如果是多个属性,那么需要进行嵌套:
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
[self willChangeValueForKey:@"itemChanged"];
_balance = theBalance;
_itemChanged = _itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"balance"];
}对于一个有序的to-many关系的属性,当它变化时,你不但要指出它的key,还要指出变化的类型以及变化元素的下标
- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
[self willChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
// Remove the transaction objects at the specified indexes.
[self didChange:NSKeyValueChangeRemoval
valuesAtIndexes:indexes forKey:@"transactions"];
}
三、关于有依赖关系的Keys kvo的处理
在许多情况下一个对象的属性值,依赖于其他对象的多个属性的值,因此当依赖对象的一个属性值发生变化时,需要收到通知。例如一个人的名字包含姓和名,获取全名的getter方法如下
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}当一个对象监听fullName的变化,它需要在firstName改变和LastName属性改变的时候都接收到通知,那么可以有两种方法来注册这种依赖关系1.重写
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key方法
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}2.实现方法,keyPahtsForValuesAffecting<Key>
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}但是对于集合类的属性这两个方法都不行,假设有一个Department对象,有一个to-many relationship的属性employees(对于Employee对象),而Employee有一个salary属性。而Department对象有一个属性totalSalary,依赖于emplyees中所有Employee对象的salary属性。那么可以Department对象注册所有employees中Employee对象属性salary的监听,并负责移除监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == totalSalaryContext) {
[self updateTotalSalary];
}
else
// deal with other observations and/or invoke super...
}
- (void)updateTotalSalary {
[self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
if (totalSalary != newTotalSalary) {
[self willChangeValueForKey:@"totalSalary"];
_totalSalary = newTotalSalary;
[self didChangeValueForKey:@"totalSalary"];
}
}
- (NSNumber *)totalSalary {
return _totalSalary;
}
- 浅谈kvo
- KVC 和KVO浅谈
- 浅谈KVC和KVO
- KVC、KVO浅谈(一)
- 浅谈IOS KVC和KVO
- KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- [iOS] KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- [iOS] KVO(NSKeyValueObserving)、KVC(NSKeyValueCoding)作用浅谈
- KVO
- kvo
- kvo
- KVO
- KVO
- Kvo
- Master-Mind Hints—UVa340
- 凸函数
- 两年Java开发工作经验面试总结
- SpringBoot简单入门(二)
- Python 是什么
- 浅谈kvo
- csdn博客转载
- 数据库的基本操作
- Ubuntu16.10 更新firefox57
- 软件测试学习笔记五课:软件质量
- STL工具库使用解析系列之二:自定义比较函数的两种方式(重载和仿函数)
- 51nod 1717 好数 (水题)
- 解决在Jupyter中import tensorflow报错
- Uva 1594