IOS开发之旅-KVO

  在设计模式中,有一种模式称为观察者模式,Objective-c也提供了相似的机制,简称为KVO【Key-Value Observing】。当被观察者的属性改变时当即通知观察者触发响应的行为。设计模式

  在KVO中,首先被观察者与观察者应该先创建关系,当被观察的特定属性改变时,马上通知观察者,创建联系调用以下方法:app

/* Register or deregister as an observer of the value at a key path relative to the receiver. The options determine what is included in observer notifications and when they're sent, as described above, and the context is passed in observer notifications as described above. You should use -removeObserver:forKeyPath:context: instead of -removeObserver:forKeyPath: whenever possible because it allows you to more precisely specify your intent. When the same observer is registered for the same key path multiple times, but with different context pointers each time, -removeObserver:forKeyPath: has to guess at the context pointer when deciding what exactly to remove, and it can guess wrong.
*/
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

  对参数options说明下,能够经过|操做符进行或操做,NSKeyValueObservingOptions定义以下:less

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
    NSKeyValueObservingOptionNew = 0x01,  //做为变动信息的一部分发送新值
    NSKeyValueObservingOptionOld = 0x02,  //做为变动信息的一部分发送旧值
    NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,  //在观察者注册时发送一个初始更新
    NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08    //在变动先后分别发送变动,而不仅在变动后发送一次
};

  addObserver的context参数与observeValueForKeyPath的context参数指向同一个地址,进行额外参数的传递。ide

  当不在须要观察者监听被观察者的属性变化时,必须移除这种监听关系,不然程序会抛出异常,调用以下方法移除监听关系:this

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

  当观察者接受到被观察者属性改变的通知时,当即调用特定的方法以示响应,观察者必须实现以下方法:atom

/* Given that the receiver has been registered as an observer of the value at a key path relative to an object, be notified of a change to that value.

The change dictionary always contains an NSKeyValueChangeKindKey entry whose value is an NSNumber wrapping an NSKeyValueChange (use -[NSNumber unsignedIntegerValue]). The meaning of NSKeyValueChange depends on what sort of property is identified by the key path:
    - For any sort of property (attribute, to-one relationship, or ordered or unordered to-many relationship) NSKeyValueChangeSetting indicates that the observed object has received a -setValue:forKey: message, or that the key-value coding-compliant set method for the key has been invoked, or that a -willChangeValueForKey:/-didChangeValueForKey: pair has otherwise been invoked.
    - For an _ordered_ to-many relationship, NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, and NSKeyValueChangeReplacement indicate that a mutating message has been sent to the array returned by a -mutableArrayValueForKey: message sent to the object, or sent to the ordered set returned by a -mutableOrderedSetValueForKey: message sent to the object, or that one of the key-value coding-compliant array or ordered set mutation methods for the key has been invoked, or that a -willChange:valuesAtIndexes:forKey:/-didChange:valuesAtIndexes:forKey: pair has otherwise been invoked.
    - For an _unordered_ to-many relationship (introduced in Mac OS 10.4), NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, and NSKeyValueChangeReplacement indicate that a mutating message has been sent to the set returned by a -mutableSetValueForKey: message sent to the object, or that one of the key-value coding-compliant set mutation methods for the key has been invoked, or that a -willChangeValueForKey:withSetMutation:usingObjects:/-didChangeValueForKey:withSetMutation:usingObjects: pair has otherwise been invoked.

For any sort of property, the change dictionary contains an NSKeyValueChangeNewKey entry if NSKeyValueObservingOptionNew was specified at observer registration time, it's the right kind of change, and this isn't a prior notification. The change dictionary contains an NSKeyValueChangeOldKey if NSKeyValueObservingOptionOld was specified and it's the right kind of change. See the comments for the NSKeyValueObserverNotification informal protocol methods for what the values of those entries can be.

For an _ordered_ to-many relationship, the change dictionary always contains an NSKeyValueChangeIndexesKey entry whose value is an NSIndexSet containing the indexes of the inserted, removed, or replaced objects, unless the change is an NSKeyValueChangeSetting.

If NSKeyValueObservingOptionPrior (introduced in Mac OS 10.5) was specified at observer registration time, and this notification is one being sent prior to a change as a result, the change dictionary contains an NSKeyValueChangeNotificationIsPriorKey entry whose value is an NSNumber wrapping YES (use -[NSNumber boolValue]).

context is always the same pointer that was passed in at observer registration time.
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

  对于observeValueForKeyPath的字典类型的change参数的key可能包含以下的值,具体包含什么key,是由调用addObserver时设置的options参数的值spa

FOUNDATION_EXPORT NSString *const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSString *const NSKeyValueChangeNotificationIsPriorKey NS_AVAILABLE(10_5, 2_0);

  NSKeyValueChangeKindKey可能取以下的枚举值:设计

/* Possible values in the NSKeyValueChangeKindKey entry in change dictionaries. See the comments for -observeValueForKeyPath:ofObject:change:context: for more information.
*/
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4
};

  以上介绍只针对单属性的监听,有时候可能会遇到组合属性的监听,好比说一个用户的fullName由firstName、lastName构成,那么无论是firstName仍是lastName被改变了,fullName都会跟随改变,若是观察者监听了fullName,那么firstName、lastName改变时候都应该触发观察者的observeValueForKeyPath方法,那么如何在firstName或者lastName改变的时候自动触发观察者的observeValueForKeyPath方法?观察者必须实现以下方法code

/* Return a set of key paths for properties whose values affect the value of the keyed property. When an observer for the key is registered with an instance of the receiving class, KVO itself automatically observes all of the key paths for the same instance, and sends change notifications for the key to the observer when the value for any of those key paths changes. The default implementation of this method searches the receiving class for a method whose name matches the pattern +keyPathsForValuesAffecting<Key>, and returns the result of invoking that method if it is found. So, any such method must return an NSSet too. If no such method is found, an NSSet that is computed from information provided by previous invocations of the now-deprecated +setKeys:triggerChangeNotificationsForDependentKey: method is returned, for backward binary compatibility.

This method and KVO's automatic use of it comprise a dependency mechanism that you can use instead of sending -willChangeValueForKey:/-didChangeValueForKey: messages for dependent, computed, properties.
 
You can override this method when the getter method of one of your properties computes a value to return using the values of other properties, including those that are located by key paths. Your override should typically invoke super and return a set that includes any members in the set that result from doing that (so as not to interfere with overrides of this method in superclasses).

You can't really override this method when you add a computed property to an existing class using a category, because you're not supposed to override methods in categories. In that case, implement a matching +keyPathsForValuesAffecting<Key> to take advantage of this mechanism.
*/
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

或者orm

+(NSSet*)keyPathsForValuesAffecting<Key>

  

  下面用生活中得一个例子来演示,当用户银行中得余额发生改变时应当当即通知用户改变状况,

  KVOBankObject:

#import <Foundation/Foundation.h>

@interface BankObject : NSObject
{
    int _accountBalance;
}

@property int accountBalance;

-(id)initWithAccountBalance:(int)accountBalance;

@end


#import "KVOBankObject.h" @implementation BankObject -(id)initWithAccountBalance:(int)accountBalance { if(self = [super init]) { self.accountBalance = accountBalance; } return self; } @end

  KVOPersonObject:

#import <Foundation/Foundation.h>

@interface PersonObject : NSObject
@property (nonatomic,strong) NSString* firstName;
@property (nonatomic,strong) NSString* lastName;
-(id)initWithFirstName:(NSString*)firstName LastName:(NSString*)lastName;
-(NSString*)fullName;
@end



#import "KVOPersonObject.h"
#import "KVOBankObject.h"

@implementation PersonObject

-(id)initWithFirstName:(NSString *)firstName LastName:(NSString *)lastName
{
    if(self = [super init])
    {
        self.firstName = firstName;
        self.lastName = lastName;
    }
    return self;
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    
    if ([object isKindOfClass:[BankObject class]]) {
        if([keyPath isEqualToString:NSStringFromSelector(@selector(accountBalance))])
        {
            NSLog(@"%@",change);
        }
    }
    
    if ([object isKindOfClass:[PersonObject class]]) {
        if([keyPath isEqualToString:NSStringFromSelector(@selector(fullName))])
        {
            NSLog(@"%@",change);
        }
    }
}

-(NSString*)fullName
{
    return [NSString stringWithFormat:@"%@ %@",self.firstName,self.lastName];
}

+(NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if([key isEqualToString:NSStringFromSelector(@selector(fullName))])
    {
        NSArray *affectingKeys = @[NSStringFromSelector(@selector(lastName)),NSStringFromSelector(@selector(firstName))];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

+(NSSet *)keyPathsForValuesAffectingFullName
{
    return [NSSet setWithObjects:NSStringFromSelector(@selector(lastName)),NSStringFromSelector(@selector(firstName)), nil];
}
@end

 

 调用示例

#import <Foundation/Foundation.h>
#import "KVOBankObject.h"
#import "KVOPersonObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        PersonObject *personObj = [[PersonObject alloc]init];
        BankObject *bankObj = [[BankObject alloc]initWithAccountBalance:1];
        [bankObj addObserver:personObj forKeyPath:@"accountBalance" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
        bankObj.accountBalance = 2;
        [bankObj removeObserver:personObj forKeyPath:@"accountBalance"];
    }
    return 0;
}

  由于options : NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew,因此输出属性的旧值与新值,以下所示:

{
    kind = 1;
    new = 2;
    old = 1;
}

  若是设置options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial。NSKeyValueObservingOptionInitial在观察者注册时发送一个初始更新,输出以下:

//注册时发送一个更新请求
{
    kind = 1;
    new = 1;
}
//值改变以后发送一个更新请求
{
    kind = 1;
    new = 2;
    old = 1;
}

  若是设置options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionPrior。NSKeyValueObservingOptionPrior在变动先后分别发送变动,而不仅在变动后发送一次。输出以下:

//变动前发送一个请求
{ kind
= 1; notificationIsPrior = 1; old = 1; }
//变动后发送一个请求
{ kind = 1; new = 2; old = 1; }

  下面演示组合属性fullName的监听,当firstName、lastName改变时,触发监听方法

#import <Foundation/Foundation.h>
#import "KVOBankObject.h"
#import "KVOPersonObject.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        PersonObject *personObj = [[PersonObject alloc]initWithFirstName:@"quan" LastName:@"long"];
        [personObj addObserver:personObj forKeyPath:@"fullName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
        personObj.firstName = @"pstune";    //触发一次监听
        personObj.lastName  = @".com";      //触发一次监听
        [personObj removeObserver:personObj forKeyPath:@"fullName"];
        
        
        /*
        BankObject *bankObj = [[BankObject alloc]initWithAccountBalance:1];
        [bankObj addObserver:personObj forKeyPath:@"accountBalance" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew | NSKeyValueObservingOptionPrior context:nil];
        bankObj.accountBalance = 2;
        [bankObj removeObserver:personObj forKeyPath:@"accountBalance"];
         */
    }
    return 0;
}

  以上算是对KVO的简单总结,若有不正确的地方,请给我留言。

相关文章
相关标签/搜索