KVC
键值编码的基本概念
- KVC 是 KeyValue Coding 的简称,它是一种可以直接通过字符串的名字 (key) 来访问属性的机制。使用该机制不需要调用存取方法和变量实例就可访问对象属性。本质上讲,键 - 值编码定义了你的程序存取方法需要实现的样式及方法签名。
- 在应用程序中实现键 - 值编码兼容性是一项重要的设计原则。存取方法可以加
强合适的数据封装,而键 - 值编码方法在多数情况下可简化程序代码。 - 键 - 值编码方法在 Objective-C 非标准协议(类目)NSKeyValueCoding 中被声明,默认的实现方法由 NSObject 提供,所以凡是继承自 NSObject 的类都具备 KVC 功能。
- 键 - 值编码支持带有对象值的属性,同时也支持纯数值类型和结构。非对象参
数和返回类型会被识别并自动封装 / 解封。
设置和访问
- 键 / 值编码中的基本调用包括
-valueForKey:和-setValue:forkey:这两个
方法,它们以字符串的形式向对象发送消息,字符串是我们关注属性的关键。
示例 1:
BNRAppliance *a = [[BNRAppliance alloc] init];
[a setProductName:@"Washing Machine"];
// 使用 KVC 重写以上代码
[a setValue:@"Washing Machine" forKey:@"productName"];
[a setVoltage:240];
// 使用 KVC 重写以上代码
[a setValue:[NSNumber numberWithInt:240] forKey:@"voltage"];
NSLog(@"a is %@",a);
// 使用 KVC 重写以上代码
NSLog(@"the product name is %@",[a valueForKey:@"productName"]);
示例 2:
Person *jack = [[Person alloc] init];
NSMutableString *name = [[NSMutableString alloc] initWithFormat:@"jack"];
[jack setValue:name forKey:@"name"];
NSLog(@"jack name : %@", [jack valueForKey:@"name"]);
- 使用 KVC,编译器会查找是否存在 setter、getter 方法,如果不存在,它将在内部查找名为 _key 或 key 的实例变量。通过 KVC,可以获取不存在 getter 方法的对象值,无需通过对象指针直接访问。
- KVC 能够在没有存取方法的情况下直接存取实例变量。
- ⚠️非对象类型:当我们通过
setValue:forKey:设置对象的值,或通过valueForKey来获取对象的值时,如果对象的实例变量为基本数据类型(char、int、float、BOOL)时,我们需要使用 NSNumber 对象对数据进行 封装。
key 路径
- 除了通过键设置值外,键 / 值编码还支持指定路径,像文件系统一样。用 “点”
号隔开。 - 使用 key path 可以一次性遍历复杂的对象表。
- 注意顺序,第一个想要遍历的对象放在第一个。
示例:BNRDepartment. manager 指向 BNREmployee. emergencyContact 指向 BNRPerson. phoneNumber
BNRDepartment *sales = ...;
BNREmployee *sickEmployee = [sales valueForKey:@"manager"];
BNRPerson *personToCall = [sickEmployee valueForKey:@"emergencyContact"];
[personToCall setValue:@"555-606-0842" forKey:@"phoneNumber"];
// 使用 Key路径 重写以上代码
BNRDepartment *sales = ...;
[personToCall setValue:@"555-606-0842"
forKeyPath:@"manager.emergencyContact.phoneNumber"];
一对多的关系
- 如果向 NSArray 请求一个键值,它实际上会查询数组中的每个对象来查找这个
键值,然后将查询结果打包到另一个数组中并返回给你。
NSArray *booksArray = [NSArray arrayWithObjects:book1, book2, nil];
[book1 release];
[book2 release];
[book setValue:booksArray forKey:@"relativeBooks"];
NSLog(@"books 2: %@", [book valueForKeyPath:@"relativeBooks.price"]);
实现简单的运算
NSString *count = [book valueForKeyPath:@"relativeBooks.@count"];
NSLog(@"count : %@", count);
NSString *sum = [book valueForKeyPath:@"relativeBooks.@sum._price"];
NSLog(@"sum : %@", sum);
NSString *avg = [book valueForKeyPath:@"relativeBooks.@avg._price"];
NSLog(@"avg : %@", avg);
NSString *min = [book valueForKeyPath:@"relativeBooks.@min._price"];
NSLog(@"min : %@", min);
NSString *max = [book valueForKeyPath:@"relativeBooks.@max._price"];
NSLog(@"max : %@", max);
KVO
基本概念
- Key Value Observing,直译为:基于键值的观察者。它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察对象的属性被修改后,KVO 就会自动通知相应的观察者了。
- 与 NSNotification 不同,键 - 值观察中并没有所谓的中心对象来为所有观察者提供变化通知。取而代之地,当有变化发生时,通知被直接发送至处于观察状态的对象。NSObject 提供这种基础的键 - 值观察实现方法。
- 你可以观察任意对象属性,包括简单属性,对一或是对多关系。对多关系的观察者将会被告知发生变化的类型 - 也就是任意发生变化的对象。
- 键 - 值观察为所有对象提供自动观察兼容性。你可以通过禁用自动观察通知并
实现手动通知来筛选通知。
注册观察者
为了正确接收属性的变更通知,观察对象必须首先发送一个addObserver:forKeyPath:options:context: 消息至被观察对象,用以传送观察
对象和需要观察的属性的关键路径,以便与其注册。选项参数指定了发送变更通知时提供给观察者的信息。 使用 NSKeyValueObservingOptionOld 选项可以将
初始对象值 以变更字典中的一个项的形式提供给观察者。指定NSKeyValueObservingOptionNew 选项可以将新的值以一个项的形式 添加至
变更字典。你可以使用逐位 “|” 这两个常量来指定接收上述两种类型的值。
示例:
BNRLogger *logger = [[BNRLogger alloc] init];
__unused NSTimer *timer =
[NSTimer scheduledTimerWithTimeInterval:2.0
target:logger
selector:@selector(updateLastTime:)
userInfo:nil
repeats:YES];
BNRObserver *observer = [[BNRObserver alloc] init];
// 无论 lastTime 何时发生变化,都要通知我它改变的新值以及改变之前的旧值
[logger addObserver:observer
forKeyPath:@"lastTime"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
[[NSRunLoop mainRunLoop] run];
接受变更后通知
- 当对象的一个被观察属性发生变动时,观察者收到一个
observeValueForKeyPath:ofObject:change:context:消息。所有观察者都必须
实现这一方法。触发观察通知的对象和键路径、包含变更细节的字典,以及观察者
注册时提交的上下文指针均被提交给观察者,context可以为任意类型参数。
示例:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"Observed:%@ of %@ was changed from %@ to %@",
keyPath,object,oldValue,newValue);
}
移除观察者身份
- 你可以发送一条指定观察方对象和键路径的
removeObserver:forKeyPath:
消息至被观察的对象,来移除一个键 - 值观察者。(当我们达到目的时)
示例:
[child removeObserver:self forKeyPath:@"key"];