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.