1. 概述
ObjC主要基于Smalltalk进行设计, 因此它有很多类似Ruby,Python的动态特性, 例如动态类型,动态加载,动态绑定等. 因此可以ObjC中可以使用键值编码KVC 和 键值监听KVO; 基于观察者思想:
一个目标对象 管理所有依赖于它的 观察者对象;并在自身的状态改变时 主动通知观察者对象. 通知通告调用各观察着对象所提供的接口方法实现, 观察者模式 为了解耦;
1. KVC
C#中可以通过反射读写一个对象的属性, 利用字符串的方式去动态控制一个对象. 但是对于ObjC的runtime特性, 我们不需要进行任何操作即可进行属性的动态读写,
KVC的操作方法有NSKeyValueCoding协议提供, NSObject遵守了这个协议, 所以OC的对象都可以使用KVC;
- 动态设置: setValue: forKey:属性名 (用于简单路径) / setValue: forKeyPath:属性路径(用于复合路径,即属性的某属性.例如Person有一个Account类型的属性,那么person.account就是一个复合属性)其实就是 属性链式访问
- 动态读取: valueForKey: 属性名 / valueForKeyPath: 属性路径
注意:
- KVC 可以访问私有变量.
- valueForKey会自动把基本类型转成NSNumber或NSValue中包装成对象,同样,动态设置setValue: forKey:的属性也必须先包装成NSNumber对象类型才可以.
查找规律:
- 先检查是否存在属性a的set和get方法(BOOL类型属性的get方法名是is<key>), 没有就会搜索_<key>/_set<key>方法.
- 如果还没有再搜索成员变量_a, 如果仍不存在,就会搜索成员变量a
- 如果最后仍没搜索到, 会根据设值还是取值 调用 setValue:forUndefinedKey:或valueforUndefineKey: 抛出异常.根据需要重写它们;
补充
批处理:
KVC可以对对象进行批量更改,dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:dict;
数组的整体操作:
如果向一个数组请求一个key,KVC会查询数组中每个对象来查找这个key,之后会将结果打包到一个新数组并返回;例:student有很多book, 获取book的nameNSArray *names = [student valueForKeyPath:@"books.name"];
键路径的运算符:
在路径中,可以引用一下运算符@xxxxx来进行一些运算,例如获取一组值得平均值,最值或者总数.
- 简单运算符@avg @count @max @min @sum
- 对象运算符: @distinctUnionOfObjects(去掉重复) @unionOfObjects(不去重复),都返回数组
- Array和Set操作符: 集合中包含集合的情况.
NSNumber *count = [student valueForKeyPath:@"books.@count"];//计算总数
NSNumber *sum = [student valueForKeyPath:@"books.@sum.price"];//总和
NSArray *prices = [student valueForKeyPath:@"books.@distinctUnionOfObjects.price"];
KVO
//1. 注册监听器
-(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
anObserver :监听器对象
keyPath :监听的属性
options :决定了当属性改变时,要传递什么数据给监听器
//2.监听器需要实现监听方法,来处理收到的通知;
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
keyPath :监听的属性
object :谁的属性改变了
change :属性改变时传递过来的信息(取决于添加监听器时的options参数
//3. 最后移除监听器
-(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
注册和解除注册定义在NSKeyValueObserving协议中, NSObject,NSArray,NSSet实现了此协议;
KVO实现机制:
当某个类的对象第一次被观察时,系统就会在 运行时动态的创建该类的一个派生类, 在这个派生类中重写原类中被观察属性的setter方法;
派生类在被重写的setter方法中实现真正的 通知机制. 这是基于设置属性会调用setter方法,而通过重写就可以获得KVO需要的通知机制. (所以,使用KVO要遵循其属性设置方式来改变属性值, 如果仅仅直接修改属性值,是无法实现KVO的); (补充: Swift中的属性观察器原理相似)
- (void) setAge:(int)theAge
{
// will和did两个方法用于通知系统该key的属性值即将和已经变更
[self willChangeValueForKey:@"age"];
age = theAge;
[self didChangeValueForKey:@"age"];
}
//didChangeValueForKey方法会调用下面方法,
+(BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
同时派生类还重写了class方法以"欺骗"外部调用者它就是起初的那个类.然后系统将这个对象的isa指针指向了这个新诞生的派生类, 之后调用该对象的setter就会调用重写后的setter从而激活通知
机制.当然,派生类还重写了dealloc方法来释放资源.
总结: KVO的三种方式:
- 使用了KVC情况下:如果有访问器方法,则运行时会在访问器方法中调用
will/didChangeValueForKey:
方法; 如果没有访问器方法,运行时会在setValue:forKey
方法中调用will/didChangeValueForKey:
方法; - 有访问器方法: 运行时会重写访问器方法来调用will/didChangeValueForKey:;
- 如果没有使用KVC,且没有访问器方法, 可以显示调用will/didChangeValueForKey:.就同样可以使用KVO;