美文网首页
OC中KVO属性互相影响探究

OC中KVO属性互相影响探究

作者: 传说中的汽水枪 | 来源:发表于2018-12-17 16:48 被阅读19次

    keyPathsForValuesAffectingXXXkeyPathsForValuesAffectingValueForKey这两个函数是表示若干个属性在KVO的情况下是否相互影响。
    举例来说有a和b两个属性,如果a的变化影响b,我们使用KVO监听b,当我们在更改a的时候,如何让监听的b能收到消息,上述两个方法就是解决这个问题的。
    前置说明
    RXAFNTest4Object 是包含各种互相影响的属性的类
    RXAFNTestDependPropertyObject是测试类
    RXAFNTestDependPropertyObject.m文件中:

    @interface RXAFNTestDependPropertyObject ()
    @property (nonatomic, strong) RXAFNTest4Object *test4Object;
    @end
    
    @implementation RXAFNTestDependPropertyObject
    // 监听属性变化的
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"KeyPath:%@, change.new:%@", keyPath, change[@"new"]);
    }
    @end
    

    一对一的影响:b的值依赖a(a影响b),b是只读的

    RXAFNTest4Object.h

    @property (nonatomic, assign) int a;
    @property (nonatomic, readonly) int b;
    

    RXAFNTest4Object.m

    - (int)b
    {
        return self.a + 1;
    }
    + (NSSet *)keyPathsForValuesAffectingB
    {
        return [NSSet setWithObjects:@"a", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_A_B {
        [self.test4Object addObserver:self forKeyPath:@"b" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.a = 5;
        });
    }
    

    查看observeValueForKeyPath:ofObject:change:context中的输出:

    KeyPath:b, change.new:6
    

    也就是说添加的b的监听,变化a的时候因为keyPathsForValuesAffectingB的存在,b收到这个变化了

    一对一的影响:b1的值依赖a1, 注意b1与b的区别

    RXAFNTest4Object.h

    @property (nonatomic, assign) int a1;
    @property (nonatomic, assign) int b1;
    

    RXAFNTest4Object.m

    + (NSSet *)keyPathsForValuesAffectingB1
    {
        return [NSSet setWithObjects:@"a1", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_A1_B1 {
        [self.test4Object addObserver:self forKeyPath:@"b1" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.a1 = 5;
        });
    }
    

    输出

    KeyPath:b1, change.new:0
    

    如果不考虑最终b1的值的话,效果是跟ab一致的。

    一对一的影响:d的值依赖c,在c的赋值过程中会重新赋值d

    RXAFNTest4Object.h

    @property (nonatomic, assign) int c;
    @property (nonatomic, assign) int d;
    

    RXAFNTest4Object.m

    - (void)setC:(int)c
    {
        _c = c;
        self.d = _c + 2;
    }
    + (NSSet *)keyPathsForValuesAffectingD
    {
        return [NSSet setWithObjects:@"c", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_C_D
    {
        [self.test4Object addObserver:self forKeyPath:@"d" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.c = 5;
        });
    }
    

    查看输出结果

    KeyPath:d, change.new:7
    KeyPath:d, change.new:7
    

    d的变化通知会输出两遍,出现这个原因是self.d = _c + 2;keyPathsForValuesAffectingD都会触发KVO,解决这个bug(如果这个算bug的话),让它只调用一次有两种方法:

    • 删除keyPathsForValuesAffectingD方法
    • self.d = _c + 2;改为_d = _c + 2;

    多个影响1个

    RXAFNTest4Object.h

    @property (nonatomic, assign) int c1;
    @property (nonatomic, assign) int c2;
    @property (nonatomic, assign) int c3;
    @property (nonatomic, assign) int d1;
    

    RXAFNTest4Object.m

    + (NSSet *)keyPathsForValuesAffectingD1
    {
        return [NSSet setWithObjects:@"c1", @"c2", @"c3", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_C1_C2_C3_D1
    {
        [self.test4Object addObserver:self forKeyPath:@"d1" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.c1 = 10;
            self.test4Object.c2 = 20;
            self.test4Object.c3 = 30;
        });
    }
    

    输出结果

    KeyPath:d1, change.new:0
    KeyPath:d1, change.new:0
    KeyPath:d1, change.new:0
    

    输出三遍是合理的,因为在dispatch_afterc1c2c3依次变化了

    AFN中AFSecurityPolicy中pinnedCertificates与pinnedPublicKeys属性互相通知影响

    RXAFNTest4Object.h

    // e: pinnedCertificates
    // f: pinnedPublicKeys
    @property (nonatomic, assign) int e;
    

    RXAFNTest4Object.m

    @interface RXAFNTest4Object ()
    @property (nonatomic, assign) int f;
    @end
    @implementation RXAFNTest4Object
    - (void)setE:(int)e
    {
        _e = e;
        self.f = _e + 3;
    }
    
    + (NSSet *)keyPathsForValuesAffectingF
    {
        return [NSSet setWithObjects:@"e", nil];
    }
    @end
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_E_F
    {
        [self.test4Object addObserver:self forKeyPath:@"f" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.e = 5;
        });
    }
    

    输出结果

    KeyPath:f, change.new:8
    KeyPath:f, change.new:8
    

    同样的输出两遍跟cd的效果(bug?)一样。

    一对一的影响:另一种实现方法

    RXAFNTest4Object.h

    @property (nonatomic, assign) int g;
    @property (nonatomic, assign) int h;
    

    RXAFNTest4Object.m+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

    // 注意这里的区别,g影响h,这里要返回g,并指明是g的变化影响h
        if ([key isEqualToString:@"h"]) {
            return [NSSet setWithObject:@"g"];
        }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_G_H
    {
        [self.test4Object addObserver:self forKeyPath:@"h" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.g = 5;
        });
    }
    

    输出结果

    KeyPath:h, change.new:0
    

    效果跟a1,b1一致

    一个影响多个

    RXAFNTest4Object.h

    @property (nonatomic, assign) int g1;
    @property (nonatomic, assign) int h1;
    @property (nonatomic, assign) int h2;
    @property (nonatomic, readonly) int h3;
    

    RXAFNTest4Object.m+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

    if ([key isEqualToString:@"h1"] || [key isEqualToString:@"h2"] || [key isEqualToString:@"h3"]) {
        return [NSSet setWithObject:@"g1"];
    }
    

    RXAFNTest4Object.m

    - (int)h3
    {
        return self.g1 + 3;
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_G1_H1_H2_H3
    {
        [self.test4Object addObserver:self forKeyPath:@"h1" options:NSKeyValueObservingOptionNew context:nil];
        [self.test4Object addObserver:self forKeyPath:@"h2" options:NSKeyValueObservingOptionNew context:nil];
        [self.test4Object addObserver:self forKeyPath:@"h3" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.g1 = 10;
        });
    }
    

    输出结果

    KeyPath:h3, change.new:13
    KeyPath:h2, change.new:0
    KeyPath:h1, change.new:0
    

    一影响多,同理也是可用keyPathsForValuesAffectingXXX去实现

    这个顺序是怎么定义的我是不清楚的了,这个估计需要看相关的源码吧

    互相影响

    RXAFNTest4Object.h

    @property (nonatomic, assign) int i;
    @property (nonatomic, assign) int j;
    

    RXAFNTest4Object.m

    + (NSSet *)keyPathsForValuesAffectingI
    {
        return [NSSet setWithObjects:@"j", nil];
    }
    + (NSSet *)keyPathsForValuesAffectingJ
    {
        return [NSSet setWithObjects:@"i", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_I_J_1
    {
        [self.test4Object addObserver:self forKeyPath:@"i" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.j = 10;
        });
    }
    - (void)test_dependProperty_I_J_2
    {
        [self.test4Object addObserver:self forKeyPath:@"j" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.i = 10;
        });
    }
    

    test_dependProperty_I_J_1test_dependProperty_I_J_2分别能得到想要的结果

    多个影响多个-- 方法一: 使用keyPathsForValuesAffectingXXX

    RXAFNTest4Object.h

    @property (nonatomic, assign) int l1;
    @property (nonatomic, assign) int l2;
    @property (nonatomic, assign) int m1;
    @property (nonatomic, assign) int m2;
    

    RXAFNTest4Object.m

    + (NSSet *)keyPathsForValuesAffectingM1
    {
        return [NSSet setWithObjects:@"l1", @"l2", nil];
    }
    + (NSSet *)keyPathsForValuesAffectingM2
    {
        return [NSSet setWithObjects:@"l1", @"l2", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_L1_L2_M1_M2
    {
        [self.test4Object addObserver:self forKeyPath:@"m1" options:NSKeyValueObservingOptionNew context:nil];
        [self.test4Object addObserver:self forKeyPath:@"m2" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.l1 = 10;
            self.test4Object.l2 = 10;
        });
    }
    

    输出结果

    KeyPath:m2, change.new:0
    KeyPath:m1, change.new:0
    KeyPath:m2, change.new:0
    KeyPath:m1, change.new:0
    

    输出的结果是合理的

    多个影响多个-- 方法二: 使用keyPathsForValuesAffectingValueForKey

    RXAFNTest4Object.h

    @property (nonatomic, assign) int p1;
    @property (nonatomic, assign) int p2;
    @property (nonatomic, assign) int q1;
    @property (nonatomic, assign) int q2;
    

    RXAFNTest4Object.m+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

    if ([key isEqualToString:@"q1"] || [key isEqualToString:@"q2"]) {
        return [NSSet setWithObjects:@"p1", @"p2", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_P1_P2_Q1_Q2
    {
        [self.test4Object addObserver:self forKeyPath:@"q1" options:NSKeyValueObservingOptionNew context:nil];
        [self.test4Object addObserver:self forKeyPath:@"q2" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.p1 = 10;
            self.test4Object.p2 = 10;
        });
    }
    

    输出结果

    KeyPath:q2, change.new:0
    KeyPath:q1, change.new:0
    KeyPath:q2, change.new:0
    KeyPath:q1, change.new:0
    

    l1,l2,m1,m2一致

    多个影响多个 keyPathsForValuesAffectingXXX 与 keyPathsForValuesAffectingValueForKey 一起使用

    RXAFNTest4Object.h

    @property (nonatomic, assign) int r1;
    @property (nonatomic, assign) int r2;
    @property (nonatomic, assign) int s1;
    @property (nonatomic, assign) int s2;
    

    RXAFNTest4Object.m

    + (NSSet *)keyPathsForValuesAffectingS1
    {
        return [NSSet setWithObjects:@"r1", @"r2", nil];
    }
    + (NSSet *)keyPathsForValuesAffectingS2
    {
        return [NSSet setWithObjects:@"r1", @"r2", nil];
    }
    

    RXAFNTest4Object.m+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key

    if ([key isEqualToString:@"s1"] || [key isEqualToString:@"s2"]) {
        return [NSSet setWithObjects:@"r1", @"r2", nil];
    }
    

    RXAFNTestDependPropertyObject.m

    - (void)test_dependProperty_R1_R2_S1_S2
    {
        [self.test4Object addObserver:self forKeyPath:@"s1" options:NSKeyValueObservingOptionNew context:nil];
        [self.test4Object addObserver:self forKeyPath:@"s2" options:NSKeyValueObservingOptionNew context:nil];
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.test4Object.r1 = 10;
            self.test4Object.r2 = 10;
        });
    }
    

    输出结果

    KeyPath:s2, change.new:0
    KeyPath:s1, change.new:0
    KeyPath:s2, change.new:0
    KeyPath:s1, change.new:0
    

    l1,l2,m1,m2一致

    相关文章

      网友评论

          本文标题:OC中KVO属性互相影响探究

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