keyPathsForValuesAffectingXXX
与keyPathsForValuesAffectingValueForKey
这两个函数是表示若干个属性在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
的值的话,效果是跟a
,b
一致的。
一对一的影响: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_after
中c1
,c2
,c3
依次变化了
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
同样的输出两遍跟c
和d
的效果(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_1
和test_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
一致
网友评论