KVC,Key-value coding,是一种可以实现对对象的属性进行非直接访问的一种方式,这种方式要求对象必须实现NSKeyValueCoding
协议
NSObject
实现了NSKeyValueCoding
协议的方法,所以它的子类可以直接用KVC的方法
// 直接获取或设置属性值 valueForKey:/valueForKeyPath: setValue:forKey:/setValue:forKeyPath: // 获取可变类型集合数据方法 mutableArrayValueForKey:/mutableArrayValueForKeyPath: mutableSetValueForKey:/mutableSetValueForKeyPath: 复制代码
keyPath
时,第一个属性部分是相对于message receving object
而言的
[department valueForKeyPath:@"employee.salary"]
employee
是相对于department
而言int
)等同视之当获取集合类型数据时,支持加入集合操作符,这样可以对返回的集合数据进行合并、求平均、求最值等简单操作,最终返回的是计算的结果值
操作符格式是
keypathToCollection.@collectionOperator.keypathToProperty
举例
/// 获取交易信息中最早的时间 NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"]; /// 获取所有交易信息中的交易人(payee)信息,而且交易人不重复 /// 重复的判断需要`isEqual`方法的支持 NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"]; /// 对集合的集合使用操作符 NSArray* moreTransactions = @[<# transaction data #>]; NSArray* arrayOfArrays = @[self.transactions, moreTransactions]; NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"]; 复制代码
集合操作符 | 功能 | |
---|---|---|
@count | 元素个数 | 无需keypathToProperty |
@avg/@sum | 求平均、求和 | |
@max/@min | 求最值 | |
@distinctUnionOfObjects | 聚合对象,对象不重复 | |
@unionOfObjects | 聚合对象,允许重复 | |
@distinctUnionOfArrays | 将外层数组中每个数组里的每个对象的属性进行非重复聚合 | |
@unionOfArrays | 功能和上面类似,结果有重复 | |
@distinctUnionOfSets | 对集合的集合进行非重复聚合 |
本质上,实现了NSKeyValueCoding
协议的NSObject
在执行KVC的方法时,就是通过key
去找匹配的ivar
(成员变量),然后再进行get
或set
那么最重要的也就是从key
到ivar
的查找过程了,官方有做详细说明,但由于都是文字,可能比较晦涩,这里贴上掘金上一个大佬的总结图
setter
getter
发本文时图片一直转存失败,只能放上两张图片链接了
指定一个对象的某个属性,当该属性值发生变化时,可以通知给其他对象。这个机制就叫做KVO(Key-value observing)
addObserver...
方法注册observer,observeValueForKeyPath:...
接收change通知addObserver
方法并没有对observing object、observer 和 context强引用,所以可能需要手动持有observeValueForKeyPath:
observeValueForKeyPath:
方法的执行会覆盖掉父类的static void *xxxContext = &xxxContext;
注册observers时有哪些option可以传
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) { NSKeyValueObservingOptionNew = 0x01, NSKeyValueObservingOptionOld = 0x02, NSKeyValueObservingOptionInitial API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x04, NSKeyValueObservingOptionPrior API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)) = 0x08 }; 复制代码
addObserver
方法结束前,observeValueForKeyPath:
方法就会执行一次收到的更改通知是NSDictionary<NSKeyValueChangeKey, id> *
类型
typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM; /* Keys for entries in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information. */ FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey; FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey; FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey; FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey; FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey; 复制代码
NSKeyValueChangeKindKey
对应value值有
typedef NS_ENUM(NSUInteger, NSKeyValueChange) { NSKeyValueChangeSetting = 1, NSKeyValueChangeInsertion = 2, NSKeyValueChangeRemoval = 3, NSKeyValueChangeReplacement = 4, }; 复制代码
NSKeyValueChangeIndexesKey
对应的value值是NSIndexSet
类型
对于是集合类型的observed property
,获取插入的新数据
addObserver
时指定NSKeyValueObservingOptionNew
change[NSKeyValueChangeNewKey]
即为插入对象组成的数组对于是集合类型的observed property
,获取删除的数据
addObserver
时指定NSKeyValueObservingOptionOld
change[NSKeyValueObservingOptionOld]
即为被删除对象组成的数组dependent key
是指,有一个属性A(通常是一个computed property
)的值是由其他1个或多个属性值决定,那这些属性就是A的依赖属性,即为dependent key
当使用KVO监听A时,如果依赖属性的值发生变化,我们自然的也收到A变化的通知
本小节就是为了解决该问题
该问题的处理方法根据A和依赖属性之间的对应关系决定,具体关系可以是1对1,或者1对N
比如Person
对象中fullName
这个computed property
要由firstName
和lastName
两个属性决定
有两种解决办法
在Person
中重写该系统方法
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key { NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key]; if ([key isEqualToString:@"fullName"]) { NSArray *affectingKeys = @[@"lastName", @"firstName"]; keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys]; } return keyPaths; } 复制代码
或者实现一个自定义方法keyPathsForValuesAffecting[Key]
+ (NSSet *)keyPathsForValuesAffectingFullName { return [NSSet setWithObjects:@"lastName", @"firstName", nil]; } 复制代码
比如有一个Department
,拥有一个数组类型的属性employees
,数组中是Employee
,Employee
有一个工资属性salary
,Department
有一个totalSalary
的属性表示公寓内所有雇员的工资和
当用KVO监听totalSalary
时,如果其中的雇员的工资发生变化,如何让监听totalSalary
的对象收到更新通知呢?
totalSalary
的取值由一个集合中的多个对象来确定
这种情况,1对1的方案是无法解决的
唯一的方法就是,在Department
内部,要对集合中每个对象的相应属性,这里也就是Employee
的salary
进行KVO监听,同时对Department
的employees
集合属性监听
然后数据发生变化时统一告知外部监听totalSalary
的对象
如何让类、属性支持KVO
property
添加KVO支持的逻辑Dependent Key
的情况可以通过两种形式:自动和手动
手动和自动是可以并存的,并非只能选择一种实现方式
系统借助KVC特性,在NSObject
内部做了默认实现
NSObject
内部有KVO进行发通知的默认实现NSObject
的默认实现会第一时间捕捉到,并通知给Observer
并不是系统框架中所有类的属性都支持KVO,因为很多属性并不能满足KVC或者
Dependent Key
的要求。所以要以文档说明为准
对于一些特殊情况,或者不支持KVC的情况,系统也允许我们手动实现KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
方法,通过key针对需要手动通知的属性,返回FALSE
willChangeValueForKey:
和didChangeValueForKey:
index
信息observeValueForKeyPath:
有个object
参数,表示被观察者对象