zoukankan      html  css  js  c++  java
  • 键值观察 KVO

    http://www.cnblogs.com/dyf520/p/3805297.html

    Key-Value Observing Programming Guide

    1,注册Key-Value Observing: 要实现这个目的,需要:

    1)被观察的类对你想要观察的属性必须是服从Key-Value observing的

    2)你必须注册被观察对象的观察对象,使用addObserver:forKeyPath:options:context:.

    3)观察者类必须实现observeValueForKeyPath:ofObject:change:context:

    重要提示:不是所有的类对所有的属性都服从KVO。你必须确保你拥有的类是服从KVO的

    2,注册为一个观察者:使用addObserver:forKeyPath:options:context:.

    使用选项NSKeyValueObservingOptionOld来指定初始对象值在change字典中提供给观察者。

    使用NSKeyValueObservingOptionNew选项来通过change字典提供新值。

    要项获得这两个值,需要按位OR这两个选项常量。

    当你注册一个对象为观察者时,你还可以提供一个上下文指针。当observeValueForKeyPath:ofObject:change:context被调用时,上下文指针提供给观察者。上下文指针可以是一个C指针或一个对象引用。上下文指针可以被用来作为一个唯一的标识来确定被观察的更改,或提供其他一些数据给观察者。

    提示:KVO addObserver:forKeyPath:options:context:方法不维系强引用到观察对象、被观察对象或上下文。你应该确保你维系强引用到观察、被观察、对象和上下文,如果必要的话。

    3,接收更改通知:

    当被观察的对象的属性更改时,观察者接收一个observeValueForKeyPath:ofObject:change:context:消息。所有的观察者必须实现这个方法。

    Change字典入口NSKeyValueChangeKindKey提供发生的更改类型的信息。如果被观察对象的值发生了更改,NSKeyValueChangeKindKey入口返回NSKeyValueChangeSetting。基于观察者注册的options, NSKeyValueChangeOldKey和NSKeyValueChangeNewKey入口为属性更改前和更改后的值。如果其值为标量,会自动用NSValue和NSNumber包裹。

    如果被观察的属性是一个多值属性,NSKeyValueChangeKindKey入口仍然指示对象是被添加、移除或被替换,通过返回NSKeyValueChangeInsertion、NSKeyValueChangeRemoval或NSKeyValueChangeReplacement来标识。

    Change字典的入口NSKeyValueChangeIndexesKey是一个NSIndexSet对象来指定更改的indexes。如果NSKeyValueObservingOptionVew或NSKeyValueObservingOptionOld被注册,NSKeyValueChangeOldKey和NSKeyValueChangeNewKey入口是arrays包含相关对象的更改前的值和更改后的值。

    Listing 2  Implementation of observeValueForKeyPath:ofObject:change:context:

    - (void)observeValueForKeyPath:(NSString *)keyPath

                          ofObject:(id)object

                            change:(NSDictionary *)change

                           context:(void *)context {

        if ([keyPath isEqual:@"openingBalance"]) {

            [openingBalanceInspectorField setObjectValue:

                [change objectForKey:NSKeyValueChangeNewKey]];

        }

        /*

         Be sure to call the superclass's implementation *if it implements it*.

         NSObject does not implement the method.

         */

        [super observeValueForKeyPath:keyPath

                             ofObject:object

                               change:change

                               context:context];

    }

    4,移除观察者对象:

    removeObserver:forKeyPath:消息给被观察对象。例子:

    [observedObject removeObserver:inspector forKeyPath:@"openingBalance"];

    如果上下文是一个对象,你必须对其保持一个强引用直到你移除观察者。

    5,KVO Compliance:

    1)类必须是符合KVC

    2)类为属性发出KVO更改通知

    3)基于的keys被适当地注册。

    有两种机制来确保更改通知被发出。NSObject的自动支持并被默认可用于类所有遵循KVC的属性。

    当通知被发出时,手动更改通知提供附加控制,并且需要额外的代码。你可以控制自动通知属性,通过子类化并实现automaticallyNotifiesObserversForKey:方法。

    6,自动更改通知:

    例如,下面的方法引发KVO更改通知发出。

    //调用访问方法

    [account setName:@"Savings"];

    // 使用 setValue:forKey:.

    [account setValue:@"Savings" forKey:@"name"];

    // 使用 key path, where 'account' is a kvc-compliant property of 'document'.

    [document setValue:@"Savings" forKeyPath:@"account.name"];

    // 使用 mutableArrayValueForKey: to retrieve a relationship proxy object.

    Transaction *newTransaction = <#Create a new transaction for the account#>;

    NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];

    [transactions addObject:newTransaction];

    7,手动更改通知:

    一个实现手动更改通知的类必须实现automaticallyNotifiesObserversForKey:方法。它可能同时使用自动通知和手动通知。对于其要进行手动通知的属性,他应该在automaticallyNotifiesObserversForKey:方法中将其设置为NO。如下所示:

    Listing 2  Example implementation of automaticallyNotifiesObserversForKey:

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {

        BOOL automatic = NO;

        if ([theKey isEqualToString:@"openingBalance"]) {

            automatic = NO;

        }

        else {

            automatic = [super automaticallyNotifiesObserversForKey:theKey];

        }

        return automatic;

    }

    要实现手动通知,在更改之前你调用willChangeValueForKey:,在更改之后调用didChangeValueForKey:。

    Listing 3  Example accessor method implementing manual notification

    - (void)setOpeningBalance:(double)theBalance {

        [self willChangeValueForKey:@"openingBalance"];

        _openingBalance = theBalance;

        [self didChangeValueForKey:@"openingBalance"];

    }

    你可以最小化发送不必要的通知,通过首先检查值是否发生变化。

    Listing 4  Testing the value for change before providing notification

    - (void)setOpeningBalance:(double)theBalance {

        if (theBalance != _openingBalance) {

            [self willChangeValueForKey:@"openingBalance"];

            _openingBalance = theBalance;

            [self didChangeValueForKey:@"openingBalance"];

        }

    }

    如果一个单独的操作引起多个键的值发生变化,你必须nest(筑巢)更改通知,像下面这样:

    Listing 5  Nesting change notifications for multiple keys

    - (void)setOpeningBalance:(double)theBalance {

        [self willChangeValueForKey:@"openingBalance"];

        [self willChangeValueForKey:@"itemChanged"];

        _openingBalance = theBalance;

        _itemChanged = _itemChanged+1;

        [self didChangeValueForKey:@"itemChanged"];

        [self didChangeValueForKey:@"openingBalance"];

    }

    对于一个有序的多值关联,除了指定更改的键,你还需要指定更改的类型和关联的indexes。更改的类型是一个NSKeyValueChange来标识NSKeyValueChangeInsertion、NSKeyValueChangeRemoval或NSKeyValueChangeReplacement。受影响的indexes通过NSIndexSet被传递。

    Listing 6  Implementation of manual observer notification in a to-many relationship

    - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {

        [self willChange:NSKeyValueChangeRemoval

            valuesAtIndexes:indexes forKey:@"transactions"];

        // Remove the transaction objects at the specified indexes.

        [self didChange:NSKeyValueChangeRemoval

            valuesAtIndexes:indexes forKey:@"transactions"];

    }

    8,注册Dependent Keys

    有很多时候一个属性的值基于另外一个对象的的一个或多个属性。如果一个属性的值发生更改,然后衍生的属性也应该被标志为更改。你将如何确保在这些属性发生更改时,基于它的属性通过KVO通知被发布?

    1)对于单一关联:

    你需要重载keyPathsForValuesAffectingValueForKey:或实现一个合适的方法

    例如,一个人的全名基于first name和last name。一个方法返回其全名:

    - (NSString *)fullName {

        return [NSString stringWithFormat:@"%@ %@",firstName, lastName];

    }

    一个应用观察fullName属性,必须在firstName和lastName属性发生变化时得到通知。

    一个解决办法是重载keyPathsForValuesAffectingValueForKey:来指定fullName属性基于lastName和firstName。如下所示:

    + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {

        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];

        if ([key isEqualToString:@"fullName"]) {

            NSArray *affectingKeys = @[@"lastName", @"firstName"];

            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];

        }

        return keyPaths;

    }

    你的重载需要调用super的方法,并返回一个set。

    你还可以得到同样的结果,通过实现一个类方法 keyPathsForValuesAffecting<Key>,<key>是属性的名字,第一个字母为大写,如下所示:

    + (NSSet *)keyPathsForValuesAffectingFullName {

        return [NSSet setWithObjects:@"lastName", @"firstName", nil];

    }

    当你使用类别(category)向一个已存在的类添加一个计算出来的属性时,你不能重载keyPathsForValuesAffectingValueForKey:方法,因为你不能在类别中重载方法。在这种情况中,实现一个符合的keyPathsForValuesAffecting<Key>类方法来获得这种机制的好处。

    2)对于多值关联:

    keyPathsForValuesAffectingValueForKey:方法不支持包含多值关联的key-paths。例如,假设你有一个部门对象,有很多职员(employees),并且职员有工资属性。你可能想部门对象有一个totalSalary属性,基于所有职员的工资.你不能keyPathsForValuesAffectingTotalSalary并返回employees.salary。

    这里有两种可能的解决方法:

    a) 使用KVO来注册其父(这个例子里是部门)作为其所有子(这里是职员)对象的相关属性的观察者。你必须在添加或移除子对象时,添加或移除父对象作为观察者。在其observeValueForKeyPath:ofObject:change:context:方法中,你需要更新相应的值来响应通知,并发出通知。如下所示:

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

        if (context == totalSalaryContext) {

            [self updateTotalSalary];

        }

        else

        // deal with other observations and/or invoke super...

    }

    - (void)updateTotalSalary {

        [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];

    }

    - (void)setTotalSalary:(NSNumber *)newTotalSalary {

        if (totalSalary != newTotalSalary) {

            [self willChangeValueForKey:@"totalSalary"];

            _totalSalary = newTotalSalary;

            [self didChangeValueForKey:@"totalSalary"];

        }

    }

    - (NSNumber *)totalSalary {

        return _totalSalary;

    }

    b) 如果你正在使用Core Data,你可以注册父对象到application’s notification center作为其管理的对象的上下文观察者。父对象应该响应子对象发布的相应的更改通知。

     
     
     
  • 相关阅读:
    CF459E Pashmak and Graph
    cf478D Red-Green Towers
    cf255C Almost Arithmetical Progression
    准备做,但是还没做的
    CF219C hoosing Capital for Treeland
    最小中间和
    【NO.9】jmeter
    【设计用例】站内信
    【有意思的BUG】客户端无厘头 已连网的场景初始化太慢 未连网的场景异常崩溃
    禅道BUG系统
  • 原文地址:https://www.cnblogs.com/Cheetah-yang/p/4656254.html
Copyright © 2011-2022 走看看