KVC与KVO的进阶使用

概述

本篇主要介绍键-值编码KVC,键值观察KVO的进阶使用的一些技巧主要是一下两个方面:

  • KVC的集合操作符
  • KVO的手动实现方式

KVC集合操作符

关于集合操作符在苹果官方文档搜索Collection Operators的关键字就可以查看相关的文档。建议多看官方文档,本篇介绍也是以官方文档为基础的。

如果一个对象包含一个数组或者是集合的属性那么使用valueForKeyPath获取相关的属性时可以在键的路径中插入一些函数。这些函数称为集合操作符

按照分类可以分为三类:

  • 简单的集合操作符

    • @avg
    • @count
    • @max
    • @min
    • @sum
  • 对象操作符

    • @distinctUnionOfObjects
    • @unionOfObjects
  • 数组和集合操作符

    • @distinctUnionOfArrays
    • @unionOfArrays
    • @distinctUnionOfSets

对于这些操作符使用的格式如下:

现在声明一个自定义的对象如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@interface TestModel : NSObject
@property (nonatomic,strong) NSString *title;
@property (nonatomic,strong) NSString *detail;
@property (nonatomic,strong) NSArray *strings;
-(instancetype)init;
@end

@implementation TestModel
- (instancetype)init
{
self = [super init];
if (self) {
_title = @"title";
_detail = @"detail";
_strings = @[@{
@"number":@"2"
},
@{
@"number":@"3"
},
@{
@"number":@"4"
},
@{
@"number":@"5"
},
@{
@"number":@"1"
},
@{
@"number":@"1"
}];
}
return self;
}
@end

首先声明一个TestModel类型的属性

1
@property (nonatomic,strong) TestModel *testModel;

@avg:遍历集合中的元素将它们转换为一个双精度浮点数并返回表示它们的平均数NSNumber类型的对象

1
2
3
_testModel = [[TestModel alloc] init];
id object = [self valueForKeyPath:@"testModel.strings.@avg.number"];
NSLog(@"object--%@\nclass--%@",object,[object class]);

输出:

1
2
object--2.73333333333333333333333333333333333333
class--NSDecimalNumber


@count:返回集合中对象的数量

1
2
3
 _testModel = [[TestModel alloc] init];
id object = [self valueForKeyPath:@"testModel.strings.@count"];
NSLog(@"object--%@\nclass--%@",object,[object class]);

输出:

1
2
object--6
class--__NSCFNumber


@max与@min:返回集合中的最大值与最小值

1
2
3
4
5
6
 _testModel = [[TestModel alloc] init];
id object = [self valueForKeyPath:@"testModel.strings.@max.number"];
NSLog(@"object--%@\nclass--%@",object,[object class]);
_testModel = [[TestModel alloc] init];
id object = [self valueForKeyPath:@"testModel.strings.@min.number"];
NSLog(@"object--%@\nclass--%@",object,[object class]);

输出:

1
2
3
4
object--5
class--__NSCFConstantString
object--1
class--__NSCFConstantString


@sum:遍历集合中的每一项将它们转换为一个双精度浮点数并返回表示它们的和NSNumber类型的对象

@distinctUnionOfObjects:返回集合中所有的对象如果有相同的对象那么只返回一个。

1
2
3
 _testModel = [[TestModel alloc] init];
id object = [self valueForKeyPath:@"testModel.strings.@distinctUnionOfObjects.number"];
NSLog(@"object--%@\nclass--%@",object,[object class]);

输出:

1
2
3
4
5
6
7
8
object--(
3,
5,
1,
4,
"2.4"
)
class--__NSArrayI

数组中属性number为1的值有两个此时只返回一个。


@unionOfObjects:与@distinctUnionOfObjects相反,返回所有的对象包括重复的对象。


@distinctUnionOfArrays、@unionOfArrays、@distinctUnionOfSets与上述使用方法大致相同只不过操作对象由数组里的对象变成数组里的集合。


KVO的手动实现方式

如果我们使用KVO时不想更改某个属性时立刻回调,比如更改一个属性后想要执行某个操作,这个操作执行后才希望通过收到通知那么此时就需要手动实现KVO监听。

我们手动实现KVO首先要禁用系统自带的KVO监听,禁用的方法非常简单在想要监听的对象实现automaticallyNotifiesObserversForKey并返回NO即可。对于上面的自定义模型在其内部实现以下方法来达到当title属性发生变化时不会收到通知:

1
2
3
4
5
6
7
8
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"title"]) {
return NO;
}
else
return YES;
}

手动实现KVO也是通过调用willChangeValueForKeydidChangeValueForKey这两个基类方法实现的。

1
2
3
4
  _testModel = [[TestModel alloc] init];
[_testModel addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

_testModel.title = @"chapter";

上面的写法因为禁用了title属性的自动监听功能所以当title发生变化时系统不会监听到。

1
2
3
4
5
6
7
 _testModel = [[TestModel alloc] init];
[_testModel addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

[_testModel willChangeValueForKey:@"title"];
_testModel.title = @"chapter";
// do something
[_testModel didChangeValueForKey:@"title"];

调用didChangeValueForKey时系统才会监听到title已经发生变化