KVC 探究

KVC 是 Key-Value Coding 的缩写,它是苹果 macOS 和 iOS 中 Cocoa 和 Cocoa Touch 结构中运用的一种机制。KVC 答应直接拜访目标的特点,运用字符串来标识特点名称,而不是直接调用办法或拜访实例变量。这供给了面向目标编程中更多的灵敏性和抽象性。

KVC 通常与其他的 Cocoa 和 Cocoa Touch 结构一同运用,例如 Key-Value Observing 绑定,以供给强壮和灵敏的用户界面和数据模型。

KVC 设值原理

// Person.h @interface Person : NSObject
@property (nonatomic, copy) NSString *name; 
@property (nonatomic, copy) NSString *gender; 
@end 
// Person.m 
@interface Person () 
@property (nonatomic, assign) int age; 
@end 
// 调用 
Person *p = [[Person alloc] initWithName:@"小王" gender:@"男" age:18]; 
[p print]; 
[p setValue:@28 forKey:@"age"]; 
// KVC 设值 
[p print]; 
// 打印 
名字 = 小王, 性别 = 男,私密年纪 = 18 
名字 = 小王, 性别 = 男,私密年纪 = 28

age 特点是私有变量,在这儿咱们经过 KVC 能够拜访到 Person 的私有变量,那么,KVC 是怎样协助咱们经过字符串对特点进行拜访的呢?如下图所示:

轻松掌握 Cocoa 框架下的 Key-Value 编程:KVC 和 KVO

  • 第一步,KVC 首先会按次序查找目标是否有名为 set,_set 的办法,其间 是特点名,在比如中表明 setAge,_setAge 办法。假如找到了,它会将特点值作为参数传递给这个办法,完结修正;假如没有找到,则进入第二步。
  • 第二步,KVC 会先拜访 acessInstanceVariablesDirectly 办法询问是否有拜访私有变量的权限,假如不答应,则直接进入第三步;假如答应,则按照 _、_is、、is 次序顺次查找是否有成员变量,假如有,则直接修正成员变量;假如没有,则进入第三步。
  • 第三步,目标既没有相似的办法实例变量,KVC 会直接调用 setValue:forUndefinedKey: 办法,这个办法同时会抛出一个 NSUndefinedKeyException 反常。

因此,运用KVC设置特点值时,特点名称有必要与目标实践具有的特点名称完全匹配,否则就会抛出NSUnknownKeyException反常。此外,KVC 只能用于设置目标特点,不能用于设置结构体特点或 C 数组特点,对于值类型或结构体类型,需求手动封装成 NSNumber 和 NSValue 再传入。

KVC 取值原理

KVC 同样也供给取值办法,如下代码所示:

Person *p = [[Person alloc] initWithName:@"小王" gender:@"男" age:18];
NSLog([NSString stringWithFormat:@"name = %@, age = %@", p.name, [p valueForKey:@"age"]]);

为了完成取值,KVC 同样也设计了一套相似于设值的原理,如下图所示:

轻松掌握 Cocoa 框架下的 Key-Value 编程:KVC 和 KVO

  • 第一步,KVC 首先按照 get、、is 的次序查找 getter 办法,找到调用后回来 OC 目标;假如没找到,则进入第二步。
  • 第二步,KVC 会先拜访 acessInstanceVariablesDirectly 办法询问是否有拜访私有变量的权限,假如不答应,则直接进入第三步;假如答应,则按照 _、_is、、is 次序顺次查找是否有成员变量,假如有,则回来实例变量的 OC 目标;假如没有,则进入第三步。
  • 第三步,假如目标既没有对应的实例变量,也没有对应的办法,KVC会调用目标的 valueForUndefinedKey: 办法,并将特点名称作为参数传递给它。这个办法会抛出一个 NSUndefinedKeyException 反常。

KVC 取值有一点需求咱们注意,若实例变量不是 OC 目标,KVC 会主动帮咱们转换,把值类型封装成 NSNumber,把结构体封装成 NSValue。

keyPath 运用

在实践开发中,咱们会需求拜访特点的特点,例如拜访一个 Person 目标嵌套的 Address 目标的 city 特点。在 KVC 中,keyPath 是一个由特点名和点号(.)连接起来的字符串,用于指定一个目标的特点路径。它能够用于拜访目标特点的值,也能够用于对调集特点进行过滤、映射、排序等操作。

经过 keyPath,咱们能够拜访目标的嵌套特点,能够运用 @”address.city” 拜访到 被嵌套的 Address 目标的 city 特点。keyPath 也支持一些调集操作,供给了@”avg”、@”count”、@”max”、@”min”、@”sum” 等操作。比如,在一个包含 Book 目标的数组中计算一切书的总价、均价、数量、最小价格和最大价格,如下代码所示:

Book *book1 = [Book new];
book1.name = @"The Great Gastby"; 
book1.price = 10; 
Book *book2 = [Book new]; 
book2.name = @"Time History"; 
book2.price = 20; 
Book *book3 = [Book new]; 
book3.name = @"Wrong Hole"; 
book3.price = 30; 
Book *book4 = [Book new]; 
book4.name = @"Wrong Hole"; 
book4.price = 40; 
NSArray *arrBooks = @[book1,book2,book3,book4]; 
NSNumber *sum = [arrBooks valueForKeyPath:@"@sum.price"]; 
NSLog(@"sum:%f",sum.floatValue); 
NSNumber *avg = [arrBooks valueForKeyPath:@"@avg.price"]; 
NSLog(@"avg:%f",avg.floatValue); 
NSNumber *count = [arrBooks valueForKeyPath:@"@count"]; 
NSLog(@"count:%f",count.floatValue); 
NSNumber *min = [arrBooks valueForKeyPath:@"@min.price"]; 
NSLog(@"min:%f",min.floatValue); 
NSNumber *max = [arrBooks valueForKeyPath:@"@max.price"]; 
NSLog(@"max:%f",max.floatValue);
// 输出 
sum:100.000000 
avg:25.000000 
count:4.000000 
min:10.000000 
max:40.000000

小结

咱们经过对 KVC 的学习和理解,能够发现 KVC 是一种十分有用的技术,以至于在 Swift 仍保存这个机制。合理运用 KVC,能够给咱们带来以下便当:

  • KVC 能够动态的对 Objective-C 目标的实例(包含私有实例)进行设值和取值;
  • KVC 对 Objective-C 容器 setValue:forKey: 和 valueForKey: 做了特殊优化,如 NSMutableDictionary 目标调用 setValue:forKey: 进行设值,传入的 value 为 nil 时,NSMutableDictionary 目标会主动调用 removeObject:forKey:。

KVO 探究

KVO (Key-Value Observing) 是指经过调查者模式来监听目标特点的改变。当被调查目标的特点发生改变时,KVO 会主动告诉注册的调查者目标,调查者目标能够在接收到告诉后执行相应的操作。

在 Objective-C 和 Swift 中,KVO 是一种十分常用的技术,能够协助开发者编写愈加灵敏、可扩展的代码。

KVO 运用

KVO 的运用并不杂乱,例如咱们要对一个 Person 目标的 name 特点进行调查。

第一步,先给 Person 目标注册调查者目标:在 Person 目标上调用addObserver:forKeyPath:options:context: 办法,注册调查者目标。该办法会将调查者目标添加到 Person 目标的调查者列表中,并指定要调查的特点和调查选项。如下代码所示:

// 创立一个被调查目标
Person *person = [[Person alloc] init]; 
// 注册调查者目标 
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
  • observer 是调查者目标,这儿指的是当前实例;
  • keyPath 指的是 Person 目标中被调查的 name 特点,答应嵌套;
  • options 是 NSKeyValueObservingOptions 类型的枚举,经过 options 能够界说调查行为;
  • context 表明上下文,用于区分音讯,一般传入 nil。

NSKeyValueObservingOptions 类型的枚举有四类,其间 NSKeyValueObservingOptionNew 表明 Person 目标的 name 特点修正后的新值;NSKeyValueObservingOptionOld 表明 Person 目标中 name 特点修正前的旧值;NSKeyValueObservingOptionInitial 表明 Person 目标注册了调查者后会立马调用调查者办法;NSKeyValueObservingOptionPrior 表明 Person 目标的 name 特点被修正前也会调用一次调查者办法,因此修正一次 name 特点,会调用两次调查者办法。

第二步,完成调查者办法:在调查者目标中完成 observeValueForKeyPath:ofObject:change:context:办法,该办法会在 Person 目标的特点改变时被调用,能够在该办法中完成相应的操作。如下代码所示:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) { 
        NSLog(@"Name changed to %@", change[NSKeyValueChangeNewKey]); 
    }
}
  • keyPath 是 Person 目标中被调查的特点名称,和注册时传入的 keyPath 相同是;
  • object 表明调查目标;
  • change 是一个字典,用于获取 Person 目标特点改变前后的值都;
  • context 是注册时传入的 context,用于区分不同的音讯。

第三步,修正 Person 目标的 name 特点时,需求用 KVC 或特点调用,才能触发 KVO 的机制,如下代码所示:

// 修正被调查目标的特点
person.name = @"Tom"; 
[person setValue:@"Tom" forKey:@"name"];

第四步,在不需求再监听特点改变时,需求调用removeObserver:forKeyPath:办法,将调查者目标从被调查目标的调查者列表中移除。如下代码所示:

[person removeObserver:self forKeyPath:@"name"];

KVO 原理

KVO 的原理是根据 Objective-C 的 Runtime 机制完成的,当咱们为一个目标注册调查者时,KVO 会在运行时动态生成一个新类,并将原目标的 isa 指针指向新类,然后完成对原目标的特点调查。如下图所示:

轻松掌握 Cocoa 框架下的 Key-Value 编程:KVC 和 KVO

  • KVO 会在运行时动态生成个名为 NSKVONotifying_Person 的类,该类承继自 Person 类,偏重写了 setValue:forKey: 和 valueForKey: 办法;
  • 当咱们为 Person 目标的 name 特点注册调查者后,KVO 会将 name 特点的 setter 办法替换成新的 setter,然后完成特点改变时告诉;
  • 当咱们为 Person 目标注册调查者后,KVO 会将该目标的 isa 指针指向 NSKVONotifying_Person 类,然后使得该目标的一切办法的调用都会被转发到 NSKVONotifying_Person 类中。所以当咱们调用 Person 目标 name 特点的 setter 办法,实践上调用的是 NSKVONotifying_Person 类的 setter 办法,该办法会先调用父类的 setter 办法,然后再发送告诉,告诉一切调查者特点发生了改变。

有一点咱们需求注意,KVO 只能调查目标的特点,不能调查成员变量,因为 KVO 是根据 setter 办法完成的,而成员变量的赋值不会触发 setter 办法的调用,所以 KVO 无法调查到成员变量的改变。

总结

今日咱们分享了 KVC 和 KVO,咱们显现介绍了相关的根底用法,也深入了解它们的实质,尽管咱们平常可能用的不多,但两者结合起来的功用十分强壮,咱们能够借助他们来完成目标间的解耦和模型——视图的 Data Binding,然后提高代码的可拓展性和维护性。