KVC(键值编码)
动态设置:
setValue:属性值 forKey:属性名(用于简单路径)
setValue:属性值 forKeyPath:属(用于复合路径,例如Person有一个Account类型的属性,那么person.account就是一个复合属性)
动态读取:
valueForKey:属性名
valueForKeyPath:属性名(用于复合路径)
KVO(键值监听)
常规使用
1.注册指定key路径的监听器
- (void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
方法说明
作用对象:被监听对象
参数说明:
anObserver:监听者
keyPath:被监听的属性
options:监听选项
context:任意的额外数据(上下文数据)
参数解析:
options:监听选项,可以是NSKeyValueObservingOptions选项的组合。
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) { //提供属性新值(修改后的值) NSKeyValueObservingOptionNew = 0x01, //提供属性旧值(修改前的值) NSKeyValueObservingOptionOld = 0x02, //如果指定,则在添加观察者的时候立即发送一个通知给观察者, // 并且是在注册观察者方法返回之前 NSKeyValueObservingOptionInitial = 0x04, // 如果指定,则在每次修改属性时,会在修改通知被发送之前预先发送一条通知给观察者, // 这与-willChangeValueForKey:被触发的时间是相对应的。 // 这样,在每次修改属性时,实际上是会发送两条通知。 NSKeyValueObservingOptionPrior = 0x08 };
context:可以将这些数据作为上下文数据,它会传递给观察者对象的observeValueForKeyPath:ofObject:change:context:方法。这个参数的意义在于用于区分同一对象监听同一属性(从属于同一对象)的多个不同的监听。(监听不会覆盖,只会并存)
2.删除指定key路径的监听器
- (void)removeObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
方法说明
作用对象:被监听对象
参数说明:
anObserver:监听者
keyPath:监听属性
context:上下文数据,与注册监听方法中的context对应。
3.监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
方法说明:
使用:重写该方法
参数说明:
keyPath:监听属性
object:监听对象(keyPath所属的对象)
change:监听属性的变化(字典类型)
context:数据上下文,从注册监听方法中传过来
参数解析:
change:记录监听属性的变化
// 属性变化的类型,是一个NSNumber对象,包含NSKeyValueChange枚举相关的值 NSString *const NSKeyValueChangeKindKey; // 属性的新值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting, // 且添加观察的方法设置了NSKeyValueObservingOptionNew时,我们能获取到属性的新值。 // 如果NSKeyValueChangeKindKey是NSKeyValueChangeInsertion或者NSKeyValueChangeReplacement, // 且指定了NSKeyValueObservingOptionNew时,则我们能获取到一个NSArray对象,包含被插入的对象或 // 用于替换其它对象的对象。 NSString *const NSKeyValueChangeNewKey; // 属性的旧值。当NSKeyValueChangeKindKey是 NSKeyValueChangeSetting, // 且添加观察的方法设置了NSKeyValueObservingOptionOld时,我们能获取到属性的旧值。 // 如果NSKeyValueChangeKindKey是NSKeyValueChangeRemoval或者NSKeyValueChangeReplacement, // 且指定了NSKeyValueObservingOptionOld时,则我们能获取到一个NSArray对象,包含被移除的对象或 // 被替换的对象。 NSString *const NSKeyValueChangeOldKey; // 如果NSKeyValueChangeKindKey的值是NSKeyValueChangeInsertion、NSKeyValueChangeRemoval // 或者NSKeyValueChangeReplacement,则这个key对应的值是一个NSIndexSet对象, // 包含了被插入、移除或替换的对象的索引 NSString *const NSKeyValueChangeIndexesKey; // 当指定了NSKeyValueObservingOptionPrior选项时,在属性被修改的通知发送前, // 会先发送一条通知给观察者。我们可以使用NSKeyValueChangeNotificationIsPriorKey // 来获取到通知是否是预先发送的,如果是,获取到的值总是@(YES) NSString *const NSKeyValueChangeNotificationIsPriorKey;
手动发送通知
KVO是怎么做到对对象属性值的监听的呢? NSObject提供了一个NSKeyValueObserving协议的默认实现,这个协议使得对象属性在修改时具备了发送通知的能力。 当一个对象的属性发生修改时,KVO会调用下面两个方法发送修改通知。一旦修改的属性被监听,通知就会发送给监听者。
理论上,子类不应该重写这两个方法,若要重写一定要在方法中调用父类的该方法,否则修改消息不会发送给监听者。但是如果只是监听自身的某个属性,则可以直接在这两个方法中对属性值的改变做出需要的响应,而不需要继续将消息发送出去,这时候无须为属性注册监听者。
//即将改变(值还未改变) - (void)willChangeValueForKey:(NSString *)key{ NSLog(@"%@",key); } //已经改变 - (void)didChangeValueForKey:(NSString *)key{ NSLog(@"%@",key); }
这两个方法是KVO在对象属性值发生修改时发送通知的,默认情况下属性值改变会默认调用这两个方法发送通知。为提高灵活性,我们也可以手动发送通知。如果想对某个对象属性设置不自动发送值改变时的通知,重写下面方法
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
判断key是否是对应对象属性名,是的话返回NO,这样就取消了这个对象属性自动发送修改通知的能力。
注意:
1.对于不是目标属性名的key,调用super的该方法,避免对其他属性的处理方法做了意外的修改。
2.设置了手动通知之后,只需在需要的地方视情况手动调用那两个发送通知的方法即可。
被监听对象依赖于别的属性
假如要监听一个属性,这个属性的值依赖于其他若干个属性,这个属性称为计算属性,被依赖的属性中任意一个值发生变化都会导致计算属性的值改变。对于这种情况,一个做法是放弃计算属性,转而对被依赖的每个属性设置监听,然而这种方法会使情况变得复杂。这里KVO提供了更好的处理方法。
首先设置好计算属性与依赖属性之间的依赖关系,这个依赖关系在计算属性的getter方法中体现,暂不知道还有没有什么别的设置依赖关系的方法。
//建立依赖关系(viewColor计算属性依赖于rColor、gColor、bColor)
- (UIColor *)viewColor{ return [UIColor colorWithRed:self.rColor/255.0 green:self.gColor/255.0 blue:self.bColor/255.0 alpha:1.0]; }
设置好依赖关系之后,要做的就是告诉KVO这个依赖关系的存在,重写方法:
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
在key为依赖属性名的情况下返回一个包含了依赖属性名的集合对象。也可以直接对依赖属性作用方法(以viewColor为例):
+ (NSSet *)keyPathsForValuesAffectingViewColor
做好这些准备工作之后就可以像正常一样对计算属性设置监听了,当任意一个依赖属性值发生改变时,重写了监听回调方法的监听者就会收到通知。
对集合属性的监听
对于数组、集合这样的集合对象,若是不可变的,我们只能将其当做一个整体来监听,不需要做什么特别处理。若是可变的,我们也将其看成是一个整体,但若想监听到内容的添加、删除、替换,需要用集合代理对象来处理。
以可变数组为例,直接对数组本身设置监听者,然后添加数组内容,是不会触发通知发送的,因为数组本身是当成一个个体的,内存地址没有变。因此必需借助数组代理对象来实现,这个数组代理对象并不是简单得引用原数组对象,它可以理解成是原可变数组的一个替身,它可以替代原数组做任何原本能做的事,原数组内容也跟着它一起变。
之所以用代理数组对象可以实现对内容的监听,是因为对代理数组对象的内容添加、删除、替换后,原数组的内存地址发生了改变,这样就能发送通知了。
创建代理数组对象的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
该方法定义在KVC中。
对集合属性的监听也可以手动发送通知,做法和普通属性一样。发送通知方法为
-willChange:valuesAtIndexes:forKey: -didChange:valuesAtIndexes:forKey: