IOS开发之旅-KVO

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

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

/* 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定义如下:

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参数指向同一个地址,进行额外参数的传递。

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

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

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

/* 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参数的值

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方法?观察者必须实现如下方法

/* 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

或者

+(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的简单总结,如有不正确的地方,请给我留言。

  

时间: 2024-12-27 22:15:53

IOS开发之旅-KVO的相关文章

iOS开发——实用篇&amp;KVO与KVC详解

KVO与KVC详解 由于ObjC主要基于Smalltalk进行设计,因此它有很多类似于Ruby.Python的动态特性,例如动态类型.动态加载.动态绑定等.今天我们着重介绍ObjC中的键值编码(KVC).键值监听(KVO)特性: 键值编码KVC 键值监听KVO 键值编码KVC 我们知道在C#中可以通过反射读写一个对象的属性,有时候这种方式特别方便,因为你可以利用字符串的方式去动态控制一个对象.其实由于ObjC的语言特性,你根部不必进行任何操作就可以进行属性的动态读写,这种方式就是Key Valu

大钟的ios开发之旅(2)————简单说说ios中ARC与非ARC模式下的property的变量修饰词

/******************************************************************************************** * author:[email protected]大钟 * E-mail:[email protected] *site:http://www.idealpwr.com/ *深圳市动力思维科技发展有限公司 * http://blog.csdn.net/conowen * 注:本文为原创,仅作为学习交流使用,转

iOS开发之旅之UIViewController解析

就iOS开发来说,UIViewController就最核心的类型之一.而iOS的整个UI开发的核心思想也是MVC的架构,从UIViewController的命名就可以看出它在MVC中所扮演的角色,那就是Controller啦. Controller作为整个UI视图的控制器,对于用户的输入做出逻辑处理,例如用户点击某个按钮应该执行什么操作等:View角色只负责显示视图,view的这部分就是我们在nib或者storyboard设计的UI了.Model也就是我们的数据模型,例如从Core data中加

大钟的ios开发之旅(4)————简单谈谈ios程序界面实现的三种方式(代码创建,xib和storyboard)

/******************************************************************************************** * author:[email protected]大钟 * E-mail:[email protected] *site:http://www.idealpwr.com/ *深圳市动力思维科技发展有限公司 * http://blog.csdn.net/conowen * 注:本文为原创,仅作为学习交流使用,转

iOS开发-KVC和KVO的理解

KVC和KVO看起来很专业,其实用起来还是比较简单的,KVC(Key-value coding)可以理解为键值对编码,如果对象的基本类型,那么键值对编码实际上和get,set方法没有区别,如果是属性是另外一个对象,那么发现KVC用起来还是非常顺手,KVO(key-value observing)是键值对的观察者模式,如果对象的属性发生变更,那么会触发observeValueForKeyPath事件,KVO的这种通知特性让我们在开发的时候节省了不必要的代码,提高了开发效率. KVC键值对编码 KV

【iOS开发之旅】第一个iOS程序

启动界面:开发环境版本:模拟器运行效果:main.m // // main.m // 01-加法计算器 // // Created by ChenQianPing on 16/1/20. // Copyright © 2016年 chenqp. All rights reserved. // #import <UIKit/UIKit.h> #import "AppDelegate.h" // IOS程序是从main开始运行的 int main(int argc, char

【iOS开发之旅】iOS系统架构

iOS的系统架构分为四个层次:核心操作系统层(Core OS ).核心服务层(Core Services ).媒体层(Media )和可触摸层(Cocoa Touch ).下面是IOS系统结构图. 一.Core OS(核心操作系统层) 是用FreeBSD和Mach所改写的Darwin, 是开源.符合POSIX标准的一个Unix核心.这一层包含或者说是提供了整个iPhone OS的一些基础功能,比如:硬件驱动, 内存管理,程序管理,线程管理(POSIX),文件系统,网络(BSD Socket),以

iOS开发之旅之App的生命周期

在iOS App中,入口函数并不在根目录下,而是在"Supporting Files"目录的main.m文件的main函数中.这很容易理解,C/C++都是以main为入口. int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 这个函数比较简单,只是调用了UI

IOS开发之旅-KVC【键值编码】

在日常开发中,读取修改对象的属性值时,通常是点调用对应的属性进行相关操作.另外一种方式是通过键值编码,简称KVC,在键值编码中主要使用以下方法 /* Given a key that identifies an attribute or to-one relationship, return the attribute value or the related object. Given a key that identifies a to-many relationship, return a