对于私有属性或成员变量如何进行读写?
KVC
KVC(Key Value Coding)键值编码,可以通过Key名直接读写对象属性或成员变量,即使有些成员变量是私有的也可以。KVC是一个基于NSKeyValueCoding
非正式协议实现的机制,KVC的定义是通过NSObject
的分类NSKeyValueCoding
实现的,所以一切继承自NSObject
的对象都可以实现KVC。
案例一(属性)
案例一声明:
1 | @interface YBPerson : NSObject |
案例一使用:
1 | YBPerson *person = [[YBPerson alloc] init]; |
我们正常访问一个属性是通过上述点语法的方式,如果是成员变量如何访问呢?
案例二(成员变量-公共)
案例二声明:
1 | @interface YBPerson : NSObject |
案例二使用:
1 | YBPerson *person = [[YBPerson alloc] init]; |
上面案例二可以看到成员变量使用了@public
关键词修饰,如果不修饰或使用@private
还能正常读写么?
案例三(成员变量-私有)
案例三声明:
1 | @interface YBPerson : NSObject |
案例三使用:
1 | YBPerson *person = [[YBPerson alloc] init]; |
案例三代码编译报错
案例四(KVC)
案例四声明:
1 | // 人 |
案例四使用:
1 | YBPerson *person = [[YBPerson alloc] init]; |
我们可以看下相关方法的注释说明,官方文档写的非常非常详细,具体就不在此翻译了,接下来只说重点。
写操作:
1 | - (void)setValue:(nullable id)value forKey:(NSString *)key; |
setValue:forKey:
该方法的key值只能是一个属性,不能多级使用(不能使用点语法);setValue:forKeyPath:
key值可以多级属性(使用点语法),将来系统会自动解析,该方法可以直接给单个属性赋值(即上面的方法),所以一般使用该方法;setValuesForKeysWithDictionary:
可以接收一个字典,字典的key和value对应属性名和属性值;setValue:forUndefinedKey:
系统自动调用,当key或keyPath找不到时,系统会触发该方法,无须我们手动调用,但我们可以利用(重写)该方法做一些拦截操作;setNilValueForKey:
如果设置的value是空的,会调用该方法,同setValue:forUndefinedKey:
一样,都是系统自动调用,我们可以重写进行拦截。
读操作:
1 | - (nullable id)valueForKey:(NSString *)key; |
valueForKey:
对应的是setValue:forKey:
valueForKeyPath:
对应的是setValue:forKeyPath:
valueForUndefinedKey:
系统自动调用,当key不存在时会触发该方法,无须我们手动调用,但我们可以利用(重写)该方法做一些拦截操作。
扩展
使用KVC还可以进行一些计算操作。虽然并不常用,但可以作为一个了解,正常开发中我们会使用C语言库中的math
函数(OC已将这些标准库集成到开发环境中)
1 | YBPerson *person1 = [[YBPerson alloc] init]; |
KVO
KVO(Key Value Observing)键值监听,其实是一种观察者模式,为对象的某一个属性值变化进行监听。KVO也是通过NSObject
的分类NSKeyValueObserverRegistration
实现的,所以一切继承自NSObject
的对象都可以实现KVO。
NSKeyValueObserverRegistration
一共提供了三个方法:
添加观察者
1 | - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; |
observer
:观察者keyPath
:需要观察的属性options
:观察配置选项(观察值变化,并把需要观察的属性值传递给观察方法)NSKeyValueObservingOptionNew
修改之前的值NSKeyValueObservingOptionOld
修改之后的值NSKeyValueObservingOptionInitial
属性被重新赋值,注册后会立马调用一次(之后只要属性被重新赋值一次就会调用一次)NSKeyValueObservingOptionPrior
值修改前后各调用一次
context
:上下文,可以携带参数(移除观察者时必须匹配)
移除观察者
1 | - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)); |
上面的两个移除方法,一个有context
,一个没有。官方建议使用removeObserver:forKeyPath:context:
,因为该方法更加精确知道要移除哪个观察者。
如果观察同一个对象同一个属性不同的context
,观察结束后使用removeObserver:forKeyPath:
进行移除则会发生未知错误,因为系统不知道你要移除哪个观察者(毕竟上下文不一样)。如果使用context
进行校验,则很清楚知道移除哪个观察者。
案例
案例声明
1 | @interface YBPerson : NSObject |
案例使用
1 | YBPerson *person = [[YBPerson alloc] init]; |
上面案例可以看到各种options
的输出,开发中最常用就是NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
。每次在不需要观察后,记得把观察者移除[self.person removeObserver:self forKeyPath:@"age"];
,如果有context
,则使用[self.person removeObserver:self forKeyPath:@"age" context:@"idbeny"];
。
KVO的本质就是在底层监听并执行了属性的setter
方法
- 值改变之前调用
willChangeValueForKey:
; - 修改值;
- 修改值后调用
didChangeValueForKey:
; - 给观察对象发送消息
observeValueForKeyPath:ofObject:change:context
在被观察者对象的实现文件里,重写willChangeValueForKey
,didChangeValueForKey
做拦截操作。