iOS Objective-C KVO 常见用法

iOS Objective-C KVO 常见用法

前言

KVOKey-Value Observing是苹果提供给开发者的一套键值观察的API,在我们日常开发中经常用到KVO进行属性的观察,接下来我们将通过该篇文章对KVO的常见用法进行总结。

1. 观察属性的变化

1.1 基本的属性观察代码

我们最简单的使用KVO就是观察属性的变化,在本例中,我们观察person对象的name的改变。

添加观察代码:

[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:NULL];

监听观察代码:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
  
    NSLog(@"%@ - %@",self, change);
    
}

移除代码:

- (void)dealloc{
    [self.person removeObserver:self forKeyPath:@"name"];
}

1.2 一些问题

Question?

在上面我们是观察person对象name属性的变化,如果我们同样有一个新的student对象也有个name属性,那么我们在监听代码处该如何判断是那个对象的name呢?

Answer:

  • 首先我们可以通过对object的判断是否是要观察的对象,但是这样写代码就显得很low,并且需要观察的对象如果太多了,代码看起来很复杂
  • 然后我们还可以用context,它的定义是(nullable void *)类型,平常我们基本都不用这个属性,只是传个nil,其实按照官方的说法则应该传NULL,其实我们可以通过context字段来区分不同的对象属性监听。例如:

static void *PersonNameContext  = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;


[self.person addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:PersonNameContext];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:StudentNameContext];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
  
    if (context == PersonNameContext) {
        NSLog(@"person name change %@ - %@",self, change);
    } else if (context == StudentNameContext) {
        NSLog(@"student name change %@ - %@",self, change);
    }
}

2. 嵌套观察

其实在我们的开发中经常会有显示下载进度的需求,下载进度由 totalCountcompletedCount 两个值组成,如果我们观察这两个值就需要在两个地方处理进度的改变,这时候我们希望通过观察一个downloadProgress就能实现获取下载进度的需求。

2.1 实现代码

相关属性:

// 相关属性
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double completedCount;
@property (nonatomic, assign) double totalCount;

downloadProgress Getter:

- (NSString *)downloadProgress{
    if (self.completedCount <= 0) {
        self.completedCount = 0;
    }
    if (self.totalCount <= 0) {
        return @"0";
    }
    return [[NSString alloc] initWithFormat:@"%f",1.0f*self.completedCount/self.totalCount];
}

keyPathsForValuesAffectingValueForKey:

通过keyPathsForValuesAffectingValueForKey方法实现多键值的观察。

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalCount", @"completedCount"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

添加观察代码:

[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];

监听观察代码:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
  
    NSLog(@"%@ - %@",self, change);
    
}

totalCountcompletedCount任一值改变后都可以触发downloadProgress的改变,在这里最重要的就是通过+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key方法实现一组键值的观察。

3. 观察可变集合

这里我们在KVC那篇文章中有提到过,对于可变数组的观察需要使用mutableArrayValueForKey等相关方法。其实主要原因是KVO观察属性是基于Setter方法,但是直接调用可变数组的addObject方法是不走Setter的,所以此时无法实现可变数组的观察,其他可变集合同理。

添加观察代码:

[self.person addObserver:self forKeyPath:@"dataArray" options:(NSKeyValueObservingOptionNew) context:NULL];

监听和移除代码跟上面的类似。这里主要是改变的代码会有不同。

改变数组的代码:

// 数组变化(不会触发改变)
[self.person.dataArray addObject:@"1"];
// 数组变化(会触发改变)
[[self.person mutableArrayValueForKey:@"dateArray"] addObject:@"2"];

打印结果:

打印结果.jpg

通过上图结果我们可以知道,添加@"1"的时候并不会触发KVO,在添加@"2"的时候触发了KVO的监听改变,所以对于可变集合的KVO的监听需要用对应的mutableXXXValueForKey方法进行改变。

4. 自动观察与手动观察

默认情况下,我们只需要按照上面的代码就可以实现属性的观察,其实这是由系统完全控制的,属于自动观察。其实KVO还给我们提供了手动观察的选项。

如果我们想要开启手动观察就要通过重写类方法+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key,如果返回YES就是自动观察,返回NO就是手动观察,根据方法的我们还可以判断key值对不同的key分别实现自动观察手动观察

// 自动开关
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key{
    
    if ([key isEqualToString:@"name"]) {
        return NO;
    } else {
        return YES;
    }
}

对于需要手动观察key在改变前需要调用willChangeValueForKey方法,在改变后需要调用didChangeValueForKey方法,如果不调用,就不会触发KVO的监听。

示例代码:

[self.person willChangeValueForKey:@"name"];
self.person.name  = @"null";
[self.person didChangeValueForKey:@"name"];

5. KVO是否需要移除观察

在实验中,对于普通的对象我们不进行移除并没有出现问题,但是对于单例对象就会有问题。这里就不一一验证了,总是就是一句话

一定要移除观察!
一定要移除观察!
一定要移除观察!

你可能感兴趣的:(iOS Objective-C KVO 常见用法)