博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[iOS] KVC 和 KVO
阅读量:4462 次
发布时间:2019-06-08

本文共 7138 字,大约阅读时间需要 23 分钟。

开发iOS经常会看见KVO和KVC这两个概念,特地了解了一下。

我的新博客wossoneri.com

KVC Key Value Coding

KVC是一种用间接方式访问类的属性的机制。比如你要给一个类中的属性赋值或者取值,可以直接通过类和点运算符实现,当然也可以使用KVC。不过对于私有属性,点运算符就不起作用,因为私有属性不暴露给调用者,不过使用KVC却依然可以实现对私有属性的读写。

先看一下KVC的一部分源码,当然只能看到头文件:

// NSKeyValueCoding.h@interface NSObject(NSKeyValueCoding)+ (BOOL)accessInstanceVariablesDirectly;- (nullable id)valueForKey:(NSString *)key;- (void)setValue:(nullable id)value forKey:(NSString *)key;- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;- (nullable id)valueForKeyPath:(NSString *)keyPath;- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath NS_AVAILABLE(10_7, 5_0);- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;- (nullable id)valueForUndefinedKey:(NSString *)key;- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;- (void)setNilValueForKey:(NSString *)key;- (NSDictionary
*)dictionaryWithValuesForKeys:(NSArray
*)keys;- (void)setValuesForKeysWithDictionary:(NSDictionary
*)keyedValues;@end@interface NSArray
(NSKeyValueCoding)- (id)valueForKey:(NSString *)key;- (void)setValue:(nullable id)value forKey:(NSString *)key;@end@interface NSDictionary
(NSKeyValueCoding)- (nullable ObjectType)valueForKey:(NSString *)key;@end@interface NSMutableDictionary
(NSKeyValueCoding)- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;@end@interface NSOrderedSet
(NSKeyValueCoding)- (id)valueForKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);- (void)setValue:(nullable id)value forKey:(NSString *)key NS_AVAILABLE(10_7, 5_0);@end@interface NSSet
(NSKeyValueCoding)- (id)valueForKey:(NSString *)key;- (void)setValue:(nullable id)value forKey:(NSString *)key;@end

可以看到这个类里面包含了对类NSObject,NSArray,NSDictionary,NSMutableDictionary,NSOrderedSet,NSSet的拓展。拓展的方法基本上为

- (id)valueForKey:(NSString *)key;- (void)setValue:(nullable id)value forKey:(NSString *)key;

也就是说,基本上Objective-C里所有的对象都支持KVC操作,操作包含如上两类方法,动态读取和动态设值。

好多地方说是NSObject实现了NSKeyValueCoding协议。而代码里是类的拓展。这两种说法是相通的嘛?

举个?,新建一个Command line程序:

// Account.h@interface Account : NSObject@property (nonatomic, assign) float balance;@end// Account.m@implementation Account {    float salaryPerDay;}@synthesize balance = _balance;- (void)setBalance:(float)balance {    NSLog(@"set balance invoked");    _balance = balance;}- (float)balance {    NSLog(@"get balance invoked");    return _balance;}@end// Person.h@class Account;@interface Person : NSObject {    @private    int _age;}@property (nonatomic, copy) NSString *name;@property (nonatomic, retain) Account *account;- (void)showMessage;@end// Person.m#import "Person.h"@implementation Person {    NSString *_sex;}- (void)showMessage {    NSLog(@"name = %@, age = %d, sex = %@", _name, _age, _sex);}@end// mainint main(int argc, const char * argv[]) {    @autoreleasepool {              Person *person1 = [[Person alloc] init];        [person1 setValue:@"Wossoneri" forKey:@"name"];        [person1 setValue:@25 forKey:@"age"];       //私有变量也可以访问        [person1 setValue:@"male" forKey:@"sex"];   //私有变量也可以访问                [person1 showMessage];                Account *account1 = [[Account alloc] init];        person1.account = account1;                [person1 setValue:@1000.0 forKeyPath:@"account.balance"];        [person1 setValue:@300.0 forKeyPath:@"account.salaryPerDay"];        NSLog(@"Person1`s balance is : %.2f", [[person1 valueForKeyPath:@"account.balance"] floatValue]);        NSLog(@"Person1`s salary is : %.2f", [[person1 valueForKeyPath:@"account.salaryPerDay"] floatValue]);    }    return 0;}// 输出name = Wossoneri, age = 25, sex = maleset balance invokedget balance invokedPerson1`s balance is : 1000.00Person1`s salary is : 300.00

代码说明:

  • Person类里用旧方法声明私有变量_age以及直接添加的私有成员变量_sex,同时声明一个开放的属性_name
  • 对于_name,O-C会直接为其生成对应的settergetter,所以可以通过点运算符操作属性,比如
person1.name = @"Wossoneri";
  • 可以看到KVC可以对私有变量进行操作。对于当前类的直接成员变量,把变量名作为key来访问,否则要写成keyPath来访问。
  • KVC运行时首先会优先调用属性的gettersetter,这一点可以在代码输出的第二行和第三行看到,如果没有,就会优先搜索_property,不存在则搜索property,如果仍然没有,就会调用setValue:forUndefinedKey:valueForUndefinedKey:方法

KVO Key Value Observing

KVO其实是一种观察者模式,利用它可以很容易实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身。

放一部分NSKeyValueObserving.h对于NSObject的拓展代码

@interface NSObject(NSKeyValueObserving)- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary
*)change context:(nullable void *)context;@end@interface NSObject(NSKeyValueObserverRegistration)- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;@end

从拓展名称就可以看出,使用KVO需要注册监听器,也需要删除监听器。监听过程需要使用observeValueForKeyPath回调方法。

所以使用方法就可以推测出个大概来:

  1. addObserver方法注册一个监听器
  2. 复写observeValueForKeyPath回调,获得监听到的信息,做对应操作。
  3. 使用结束removeObserver,这很重要。

然后对上面代码做一些改动,我需要对Account对象的balance做监听,当balance内容改变,我要做输出处理。

#pragma mark - For KVO- (void)setAccount:(Account *)account {    _account = account;    //add observer    [_account addObserver:self               forKeyPath:@"balance"                  options:NSKeyValueObservingOptionNew                  context:nil];}//override- (void)observeValueForKeyPath:(NSString *)keyPath                      ofObject:(id)object                        change:(NSDictionary
*)change context:(void *)context { if ([keyPath isEqualToString:@"balance"]) { NSLog(@"keyPath = %@, object = %@, newValue = %.2f, context = %@", keyPath, object, [[change objectForKey:@"new"] floatValue], context); }}- (void)dealloc { [_account removeObserver:self forKeyPath:@"balance"];}// 输出name = Wossoneri, age = 25, sex = maleset balance invokedget balance invokedkeyPath = balance, object =
, newValue = 1000.00, context = (null)set balance invokedget balance invokedkeyPath = balance, object =
, newValue = 4000.00, context = (null)get balance invokedPerson1`s balance is : 4000.00Person1`s salary is : 300.00

Swift的KVO与KVC

Swift版本的的就看这篇文章吧,内容很详细。

思考

学一个东西,要想有收获,少不了的就是思考。

对于KVC来说,使用的优势我认为是在于可以读写私有成员变量,比如一些特殊情况下需要改变私有变量,而大多数情况不需要,这时候就没必要把私有变量开放出来了,用KVC就可以。(当然开放接口也行了)

对于KVO,我觉得其运行原理就是一回调了。这个声明一个协议或者写一个block应该也是可以实现相同功能的。所以什么时候使用它,需要在实战中去想想才行。

这个知识点我没怎么用过,但我会在以后的编程实践中思考这方面的内容,考虑在不同情形下怎么才能玩好它。有收获了再回来做补充。您若有相关经验,也希望能和我分享一下。

参考

转载于:https://www.cnblogs.com/rossoneri/p/5449559.html

你可能感兴趣的文章
leetcode 412. Fizz Buzz
查看>>
对Netflix Ribbon的Loadbalancer类源码设计合理性的一点质疑
查看>>
关于日历的算法
查看>>
[QT编程]QT实现的一个渐隐渐显窗体
查看>>
在Web工程中引入Jquery插件报错解决方案
查看>>
大学总结之影响我最深的十本书
查看>>
用myEclipse连接数据源生成动态数据报表
查看>>
[myeclipse]@override报错问题
查看>>
자주 쓰이는 정규표현식
查看>>
超简单的listview单选模式SingleMode(自定义listview item)
查看>>
vue-11-路由嵌套-参数传递-路由高亮
查看>>
HDU 1199 - Color the Ball 离散化
查看>>
[SCOI2005]骑士精神
查看>>
Hibernate原理解析-Hibernate中实体的状态
查看>>
六时车主 App 隐私政策
查看>>
C语言常见问题 如何用Visual Studio编写C语言程序测试
查看>>
Web用户的身份验证及WebApi权限验证流程的设计和实现
查看>>
hdu 2098 分拆素数和
查看>>
ECMAScript6-let与const命令详解
查看>>
iOS 使用系统相机、相册显示中文
查看>>