美文网首页iOS
iOS - Key Value Observing

iOS - Key Value Observing

作者: ienos | 来源:发表于2021-03-25 16:21 被阅读0次

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


    参考链接

    一、At a Glance

    KVO 主要用于 ModelController 之间的通信

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

    >> Example

    image.png
    • 假设 Person 实例与 Account 实例进行交互,表示该人在银行的储蓄账户。Person 实例可能需要知道 Account 实例的某些方面何时发生改变

    • Person 可以定期轮询 Account 的属性 blanceinterestRate,但是这样效率低下,所以衍生出 KVO,能够在 Account 发生改变时收到通知并作出响应

    二、Registering for Key-Value Observing

    • 将观察者注册到观察对象 addObserver:forKeyPath:options:context:
    • observeValueForKeyPath:ofObject:change:context: 在观察者内部实现该方法以接收更改通知消息
    • 当观察者不再接收消息,应该使用该方法注销观察者 removeObserver:forKeyPath:,并在观察者释放之前调用该方法

    并非所有的类都支持 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];
    }
    

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

    • 如果在观察者移除时之前没有注册过,将会抛出异常
      请确保 removeObserver:forKeyPath:context:addObserver:forKeyPath:options:context: 是成对调用的;如果不能保证需要放在 try/catch 中处理可能存在的异常
    • 观察者不会在 deallocated 时自动移除自己,如果在观察者已被释放的情况下,被观察者发送变更通知,相当于发送到一个 released 对象,触发内存访问异常
    • 该协议无法查询对象是观察者和非观察者;经典做法是在观察者初始化期间(例如 init 或者 viewDidLoad)注册成为观察者,在释放过程中(dealloc)注销(注意 1: 1 配对添加和移除消息)

    三、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

    • 使用 isa-swizzling 技术;在对对象的属性注册观察者时,将修改观察对象的 isa 指针,指向一个中间类而不是真实的类,isa 指针的值不一定反映实例的实际类型
    • isa 指向对象的类,实际上是一个调度表,包含指向该类实现的方法的指针及其他数据
    • 不要依靠 isa 指针来确定类成员,相反应该使用 class 方法来确定对象实例的类

    相关文章

      网友评论

        本文标题:iOS - Key Value Observing

        本文链接:https://www.haomeiwen.com/subject/lfyhhltx.html