KVC/KVO总结

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:

 

KVO代码示例见Demo

参考文档:

http://www.cnblogs.com/kenshincui/p/3871178.html

http://southpeak.github.io/blog/2015/04/23/nskeyvalueobserving-kvo/

posted @ 2015-09-30 20:55  Lotheve  阅读(518)  评论(0编辑  收藏  举报