天天看點

Objective-C中的Key-Value Coding and Key-Value Observing(KVC與KVO)

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會通知我們。

Objective-C中的Key-Value Coding and Key-Value Observing(KVC與KVO)

以上就是KVC與KVO的全部内容。

參考自:Cocoa Programming for Mac OS X 4th Edition.