iOS

iOS - Key Value Observing

2021-03-25  本文已影响0人  ienos

Key Value Observing (KVO) - 允许将其他对象的指定属性变更通知给对象


参考链接

一、At a Glance

KVO 主要用于 ModelController 之间的通信

1. View 通过 Controller 观察 Model 的属性进行改变
2. Model 也可以观察其他 Model,甚至可以观察 Model 本身(通常用于确认从属值何时改变)

>> Example

image.png

二、Registering for Key-Value Observing

并非所有的类都支持 KVO

1. Registering as an Observer

addObserver:forKeyPath:options:context: 注册成为观察者

不强引用观察者、被观察者和上下文

Options Description
NSKeyValueObservingOptionOld 指定 change dictionary 包含变更后的旧值
NSKeyValueObservingOptionNew 指定 change dictionary 包含变更后的新值
NSKeyValueObservingOptionInitial 在注册方法 return 之前发送一个通知哦
NSKeyValueObservingOptionPrior 注册后发送一个预改变通知,change dictionary 中包括 NSKeyValueChangeNotificationIsPriorKey : @(YES)

context:
可以为每个观察到的键路径创建一个上下文,区分父类与该类观察相同键路径的情况

/// Creating context pointers
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

- (void)registerAsObserverForAccount:(Account*)account {
    [account addObserver:self
              forKeyPath:@"balance"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                 context:PersonAccountBalanceContext];
 
    [account addObserver:self
              forKeyPath:@"interestRate"
                 options:(NSKeyValueObservingOptionNew |
                          NSKeyValueObservingOptionOld)
                  context:PersonAccountInterestRateContext];
}

2. Receiving Notification of a Change

Change Dictionary Description
NSKeyValueChangeKindKey NSKeyValueChangeSetting 如果观察到对象的值已更改
NSKeyValueChangeNewKey 提供更改之后的新值
NSKeyValueChangeOldKey 提供更改之前的旧值
NSKeyValueChangeIndexesKey 对应是一个 NSIndexSet 对象, 如果 NSKeyValueChangeKindKey 对应的键是 NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, or NSKeyValueChangeReplacement,那么该值为插入、移除、替换的对象
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    if (context == PersonAccountBalanceContext) {
        // Do something with the balance…

       // if ([keyPath isEqualToString: @"keyPath"]) 
      /// 如果通过 NULL 指定上下文,则通过通知的 keypath 和 正在观察的 keypath 进行比较;

    } else if (context == PersonAccountInterestRateContext) {
        // Do something with the interest rate…
 
    } else {
        // Any unrecognized context must belong to super
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                               context:context];
    }
}

3. Removing an Object as an Observer

- (void)unregisterAsObserverForAccount:(Account *)account {
    [account removeObserver:self
                 forKeyPath:@"balance"
                    context:PersonAccountBalanceContext];
 
    [account removeObserver:self
                 forKeyPath:@"interestRate"
                    context:PersonAccountInterestRateContext];
}

在移除观察者的时候需要注意以下几点

三、Registering Dependent Keys

1. To-One Relationships

一个属性的值取决于另一个对象中一个或多个其他属性的值

keyPathsForValuesAffectingValueForKey:

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

firstNamelastName 发生改变时,需要通知观察 fullName 属性

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

/// Example 2
+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}

2. To-Many Relationships

假如有一个对象 Department,他有很多 employees;然后每一个 employee 都具有 salary 属性
我们需要通过所有 employee.salary 计算出 totalSalary
一对多(一个父项对应多个子项)

- (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;
}

如果使用的是 Core Data,可以在通知中心将父项注册为 Managed object 上下文的观察者,响应子项发布的相关变更通知

四、Key-Value Observing Implementation Details

上一篇 下一篇

猜你喜欢

热点阅读