OC中KVO属性互相影响探究

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一致

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,988评论 0 13
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,100评论 1 32
  • 《机械制图》10%(50+30=80) 单项选择题 Q-B1-E-001 L 基本幅面不能满足需要而采用加长幅面时...
    开源时代阅读 3,804评论 1 1
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,341评论 0 2
  • 前言 最先接触编程的知识是在大学里面,大学里面学了一些基础的知识,c语言,java语言,单片机的汇编语言等;大学毕...
    oceanfive阅读 3,072评论 0 7