KVC与KVO是Objective-C的关键概念,KVC指的是NSKeyValueCoding,一个非正式的协议,提供一种机制来间接访问对象的属性。KVO是一种实现KVC的关键技术之一。
一个对象拥有某些属性。比如一个Person对象有一个name和address属性,以KVC的说法,Person对象分布有一个value对应于他的name和address的key。key是一个OC字符串,它对应的值可以是任意类型的对象。从最基础的层次上看,KVC有两个方法:一个是set key的值,一个是get key的值。如下代码:
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *address;
@property(nonatomic,strong)Person *spouse;
- (instancetype)initWithName:(NSString *)n andAddress:(NSString *)add;
- (instancetype)initWithName:(NSString *)n andAddress:(NSString *)add andSpouse:(Person *)p;
@end
Person.m
#import "Person.h"
@implementation Person
- (instancetype)initWithName:(NSString *)n andAddress:(NSString *)add
{
if(self = [super init])
{
self.name = n;
self.address = add;
}
return self;
}
- (instancetype)initWithName:(NSString *)n andAddress:(NSString *)add andSpouse:(Person *)p
{
if(self = [super init])
{
self.name = n;
self.address = add;
self.spouse = p;
}
return self;
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
void changeName(Person *p,NSString *newName)
{
NSString *oriName = [p valueForKey:@"name"];
[p setValue:newName forKey:@"name"];
NSLog(@"Changed %@'s name to %@.",oriName,newName);
}
void logMarriage(Person *p)
{
NSString *personName = [p valueForKey:@"name"];
NSString *spouseName = [p valueForKeyPath:@"spouse.name"];
NSLog(@"%@ is merried to %@.",personName,spouseName);
}
int main(int argc, const char * argv[]) {
Person *spouse = [[Person alloc] initWithName:@"小雅" andAddress:@"河北"];
Person *p = [[Person alloc] initWithName:@"小明" andAddress:@"北京" andSpouse:spouse];
changeName(p,@"小李");
NSLog(@"%@", [p name]);
logMarriage(p);
return 0;
}
主函数中的changeName函数是以KVC的方式用newName替换[p valueForKey:@”name”]
现在如果Person *p有一个配偶spouse,spouse是Person类的一个对象,用KVC的方法实现logMarriage函数,其中
NSString *spouseName = [p valueForKeyPath:@"spouse.name"];
valueForKeyPath指的是找到spouse对象的name。key与key path要区分开来,key可以从一个对象中获取值,而key path可以将对个key用”.”号分割连接起来。我们的代码中:
NSString *spouseName = [p valueForKeyPath:@"spouse.name"];
/*相当于是
NSString *spouseName = [[p valueForKey:@"spouse"] valueForKey:@"name"];
*/
Key-Value Observing(KVO)指的是建立在KVC上,能够观察一个对象的KVC key path的值的变化。比如说,现在一个person对象的address发生变化,我们可以用代码进行观察,以下是3种实现的方法:
- watchPersonForChangeOfAddress:实现观察
- observeValueForKeyPath: ofObject: change: context:在被观察的key path变化时调用
- dealloc 停止观察
在项目中添加一个类。
PersonWatcher.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface PersonWatcher : NSObject
- (void)watchPersonForChangeOfAddress:(Person *)p;
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context;
@property(nonatomic,copy)NSMutableArray *m_observedPeople;
@end
PersonWatcher.m
#import "PersonWatcher.h"
static NSString *const KVO_CONTEXT_ADDRESS_CHANGED = @"KVO_CONTEXT_ADDRESS_CHANGED";
@implementation PersonWatcher
- (id)init
{
if(self = [super init])
_m_observedPeople = [NSMutableArray new];
return self;
}
- (void)dealloc
{
for(id p in _m_observedPeople)
[p removeObserver:self forKeyPath:@"address"];
_m_observedPeople = nil;
}
- (void)watchPersonForChangeOfAddress:(Person *)p
{
[p addObserver:self forKeyPath:@"address" options:0 context:(__bridge void * _Nullable)(KVO_CONTEXT_ADDRESS_CHANGED)];
[_m_observedPeople addObject:p];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if(context == (__bridge void *)(KVO_CONTEXT_ADDRESS_CHANGED))
{
NSString *name = [object valueForKey:@"name"];
NSString *address = [object valueForKey:@"address"];
NSLog(@"%@ has a new address: %@",name,address);
}
}
@end
主函数中添加如下几行代码:
PersonWatcher *pw = [PersonWatcher new];
[pw watchPersonForChangeOfAddress:p];
p.address = @"辽宁";
p.address = @"山东";
这个时候,当一个Person对象的地址属性发生改变的时候,PersonWatcher会通知我们。

以上就是KVC与KVO的全部内容。
参考自:Cocoa Programming for Mac OS X 4th Edition.