KVC源码阅读。


一、接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@interface NSObject(NSKeyValueCoding)
// 是否可以直接访问实例变量(实例变量访问开关, 默认YES)
@property (class, readonly) BOOL accessInstanceVariablesDirectly;

// 通过key访问
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 尝试验证将要设定的value(ioValue指针指向的对象)是否合理有效
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// 获取相对应的精确容器类型
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

// 通过keyPath访问
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

// 查找key失败默认抛出异常, 可重写自行实现
- (nullable id)valueForUndefinedKey:(NSString *)key;
// 设置value失败默认抛出异常, 可重写自行实现
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// 设置value为nil时抛出异常, 可重写自行实现
- (void)setNilValueForKey:(NSString *)key;
// 传入key数组, 返回一个成员变量名和变量值的键值对组成的字典(可用于模型转字典)
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
// 字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
@end


/// 容器扩展
@interface NSArray<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
@interface NSDictionary<KeyType, ObjectType>(NSKeyValueCoding)
- (nullable ObjectType)valueForKey:(NSString *)key;
@end
@interface NSMutableDictionary<KeyType, ObjectType>(NSKeyValueCoding)
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
@end
@interface NSSet<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
@interface NSOrderedSet<ObjectType>(NSKeyValueCoding)
- (id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end

集合代理对象

这里简单总结集合代理对象的使用。

当我们在对象上调用 -valueForKey: 的时候,它可以返回 NSArrayNSSet 或是 NSOrderedSet 的集合代理对象。这个类没有实现通常的 -<Key> 方法,但是它实现了代理对象所需要使用的很多方法。

NSArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@interface Primes : NSObject
@property (nonatomic, copy, readonly) NSArray *primes;
@end
@implementation Primes
@dynamic primes; // 不要生成get方法
static int32_t const primes[] = {
2, 101, 233, 383, 3, 103, 239, 389, 5, 107, 241, 397, 7, 109,
251, 401, 11, 113, 257, 409, 13, 127, 263, 419, 17, 131, 269,
421, 19, 137, 271, 431, 23, 139, 277, 433, 29, 149, 281, 439,
31, 151, 283, 443, 37, 157, 293, 449, 41, 163, 307, 457, 43,
167, 311, 461, 47, 173, 313, 463, 53, 179, 317, 467, 59, 181,
331, 479, 61, 191, 337, 487, 67, 193, 347, 491, 71, 197, 349,
499, 73, 199, 353, 503, 79, 211, 359, 509, 83, 223, 367, 521,
89, 227, 373, 523, 97, 229, 379, 541, 547, 701, 877, 1049,
557, 709, 881, 1051, 563, 719, 883, 1061, 569, 727, 887,
1063, 571, 733, 907, 1069, 577, 739, 911, 1087, 587, 743,
919, 1091, 593, 751, 929, 1093, 599, 757, 937, 1097, 601,
761, 941, 1103, 607, 769, 947, 1109, 613, 773, 953, 1117,
617, 787, 967, 1123, 619, 797, 971, 1129, 631, 809, 977,
1151, 641, 811, 983, 1153, 643, 821, 991, 1163, 647, 823,
997, 1171, 653, 827, 1009, 1181, 659, 829, 1013, 1187, 661,
839, 1019, 1193, 673, 853, 1021, 1201, 677, 857, 1031,
1213, 683, 859, 1033, 1217, 691, 863, 1039, 1223, 1229,
};

- (NSUInteger)countOfPrimes {
return (sizeof(primes) / sizeof(*primes));
}

- (id)objectInPrimesAtIndex:(NSUInteger)idx {
NSParameterAssert(idx < sizeof(primes) / sizeof(*primes));
return @(primes[idx]);
}
@end

对于NSArray, 实现-countOf<Key>方法,-objectIn<Key>AtIndex:或者-<key>AtIndexes:中的一个即可,当然如果再实现-get<Key>:range:将会增强性能。

上面的例子中,key是“primes”,实际上并没有这个primes数组,而是用了一个C数组代理了。@property (nonatomic, copy, readonly) NSArray *primes;@dynamic primes; 这两句话可以省略(下文的例子就省略了),这里加上的原因是,便于外界知晓具体的key值。

使用:

1
2
3
Primes *primes = [Primes new];
// obj is kind of class 'NSKeyValueArray'
id obj = [primes valueForKey:@"primes"];

可见,获得的对象并不是一个NSArray,而是NSKeyValueArray

NSSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface PrimesSet : NSObject
@end
@implementation PrimesSet
static NSSet *_numSet;
- (instancetype)init {
if (self = [super init]) {
_numSet = [NSSet setWithObjects:@0, @1, @2, @3, nil];
}
return self;
}

- (NSUInteger)countOfPrimes {
return _numSet.count;
}

- (NSEnumerator *)enumeratorOfPrimes {
return _numSet.objectEnumerator;
}

- (id)memberOfPrimes:(id)obj {
return [_numSet member:obj];
}
@end

对于NSSet,要实现-countOf<Key>-enumeratorOf<Key>-memberOf<Key>:这三个方法。

使用:

1
2
3
PrimesSet *primesSet = [PrimesSet new];
// obj is kind of class 'NSKeyValueSet'
id obj = [primesSet valueForKey:@"primes"];

获取到的对象是NSKeyValueSet

NSOrderedSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@interface PrimesOrderedSet : NSObject
@end
@implementation PrimesOrderedSet
static NSOrderedSet *_numOrderedSet;
- (instancetype)init {
if (self = [super init]) {
_numOrderedSet = [NSOrderedSet orderedSetWithObjects:@0, @1, @2, @3, nil];
}
return self;
}

- (NSUInteger)countOfPrimes {
return _numOrderedSet.count;
}

- (NSUInteger)indexInPrimesOfObject:(id)obj {
return [_numOrderedSet indexOfObject:obj];
}

- (id)objectInPrimesAtIndex:(NSUInteger)idx {
return [_numOrderedSet objectAtIndex:idx];
}

// 用于提高性能
- (void)getPrimes:(id __unsafe_unretained *)buffer range:(NSRange)inRange {
// 返回提供的缓冲区内指定范围内的数据集合
[_numOrderedSet getObjects:buffer range:inRange];
}
@end

必须实现的方法是-countOf<Key>-indexIn<Key>OfObject:。二选一实现的方法是-objectIn<Key>AtIndex:-<key>AtIndexes:。如果再实现-get<Key>:range:将会增强性能。

使用:

1
2
3
PrimesOrderedSet *primesOrderedSet = [PrimesOrderedSet new];
// obj is kind of class 'NSKeyValueOrderedSet'
id obj = [primesOrderedSet valueForKey:@"primes"];

获取到的对象是NSKeyValueOrderedSet

集合操作

数组最大值:

1
2
NSArray *array = @[@1, @8, @5];
[array valueForKeyPath:@"@max.self"];

模型数组最大值:

1
2
NSArray *array = @[person1, person2, person3];
[array valueForKeyPath:@"@max.age"];

其他操作符:

1
2
3
4
5
6
7
8
9
10
11
12
@max @min: 获得数组中最大(或者最小)的一个元素
@avg: 将集合中对象转换成double类型,返回数组中指定的平均值的number对象
@sum: 将集合中每个对象都转换成double类型,然后计算总和,最后返回一个值为这个总和的NSNumber对象
@count:返回集合中对象总数的NSNumber对象

返回一个由操作符右边的key path指定的对象属性组成的数组,distincUnionOfObjects会对数组去重。
示例:
[personList valueForKeyPath:@"@unionOfObjects.name"];
[personList valueForKeyPath:@"@distinctUnionOfObjects.name"];
操作对象:@unionOfObjects/@distincUnionOfObjects
操作数组:@distinctUnionOfArrays/@unionOfArrays
操作集合:@distinctUnionOfSets@distinctUnionOfArrays

分类的KVC

一般的场景是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 主类
@interface Portion : NSObject
@end
@implementation Portion
@end

// 分类
@interface Portion (PrimitiveAccessors)
- (NSNumber *)primitiveVolume;
- (void)setPrimitiveVolume:(NSNumber *)value;
@end
@implementation Portion (PrimitiveAccessors)
static NSNumber *gVolume;
- (NSNumber *)primitiveVolume {
return gVolume;
}
- (void)setPrimitiveVolume:(NSNumber *)value {
gVolume = value;
}
@end

对于主类中没有的key,分类实现特定的方法后,KVC也将会生效。如果是取值,分类必须实现这样的方法:getPrimitive<key>或者primitive<key>。上面的例子中,key是volume。如果是设值,分类必须实现setPrimitive<key>:方法。

使用:

1
2
3
Portion *p = [Portion new];
[p setValue:@234 forKey:@"volume"];
id m = [p valueForKey:@"volume"];

当然,本质上来讲,KVC并不介意这些方法在主类还是分类实现的,只要有实现就成。上面的只是一个例子,实际上,完全依靠主类也是无妨的。

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface Portion :  NSObject
- (NSNumber *)primitiveVolume;
- (void)setPrimitiveVolume:(NSNumber *)value;
@end
@implementation Portion
static NSNumber *gVolume;
- (NSNumber *)primitiveVolume {
return gVolume;
}
- (void)setPrimitiveVolume:(NSNumber *)value {
gVolume = value;
}
@end

二、取值

valueForKey:

苹果在接口这里已经给出了其基本原理:

The default implementation of this method does the following:

  1. Searches the class of the receiver for an accessor method whose name matches the pattern -get<Key>, -<key>, or -is<Key>, in that order. If such a method is found it is invoked. If the type of the method’s result is an object pointer type the result is simply returned. If the type of the result is one of the scalar types supported by NSNumber conversion is done and an NSNumber is returned. Otherwise, conversion is done and an NSValue is returned (new in Mac OS 10.5: results of arbitrary type are converted to NSValues, not just NSPoint, NRange, NSRect, and NSSize).

  2. (introduced in Mac OS 10.7). Otherwise (no simple accessor method is found), searches the class of the receiver for methods whose names match the patterns -countOf<Key> and -indexIn<Key>OfObject: and -objectIn<Key>AtIndex:(corresponding to the primitive methods defined by the NSOrderedSet class) and also -<key>AtIndexes: (corresponding to -[NSOrderedSet objectsAtIndexes:]). If a count method and an indexOf method and at least one of the other two possible methods are found, a collection proxy object that responds to all NSOrderedSet methods is returned. Each NSOrderedSet message sent to the collection proxy object will result in some combination of-countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:, and -<key>AtIndexes: messages being sent to the original receiver of -valueForKey:. If the class of the receiver also implements an optional method whose name matches the pattern -get:range: that method will be used when appropriate for best performance.

  1. Otherwise (no simple accessor method or set of ordered set access methods is found), searches the class of the receiver for methods whose names match the patterns -countOf<Key> and -objectIn<Key>AtIndex:(corresponding to the primitive methods defined by the NSArray class) and (introduced in Mac OS 10.4) also-<key>AtIndexes:(corresponding to -[NSArray objectsAtIndexes:]). If a count method and at least one of the other two possible methods are found, a collection proxy object that responds to all NSArray methods is returned. Each NSArray message sent to the collection proxy object will result in some combination of -countOf<Key>, -objectIn<Key>AtIndex:, and -<key>AtIndexes: messages being sent to the original receiver of -valueForKey:. If the class of the receiver also implements an optional method whose name matches the pattern -get<Key>:range: that method will be used when appropriate for best performance.

  2. (introduced in Mac OS 10.4). Otherwise (no simple accessor method or set of ordered set or array access methods is found), searches the class of the receiver for a threesome of methods whose names match the patterns -countOf<Key>, -enumeratorOf<Key>, and -memberOf<Key>: (corresponding to the primitive methods defined by the NSSet class). If all three such methods are found a collection proxy object that responds to all NSSet methods is returned. Each NSSet message sent to the collection proxy object will result in some combination of -countOf<Key>, -enumeratorOf<Key>, and -memberOf<Key>:messages being sent to the original receiver of -valueForKey:.

  3. Otherwise (no simple accessor method or set of collection access methods is found), if the receiver’s class’ +accessInstanceVariablesDirectly property returns YES, searches the class of the receiver for an instance variable whose name matches the pattern _<key>, _is<Key>, <key>, or is<Key>, in that order. If such an instance variable is found, the value of the instance variable in the receiver is returned, with the same sort of conversion to NSNumber or NSValue as in step 1.

  4. Otherwise (no simple accessor method, set of collection access methods, or instance variable is found), invokes -valueForUndefinedKey: and returns the result. The default implementation of -valueForUndefinedKey: raises an NSUndefinedKeyException, but you can override it in your application.

Compatibility notes:

  • For backward binary compatibility, an accessor method whose name matches the pattern -_get<Key>, or -_<key> is searched for between steps 1 and 3. If such a method is found it is invoked, with the same sort of conversion to NSNumber or NSValue as in step 1. KVC accessor methods whose names start with underscores were deprecated as of Mac OS 10.3 though.
  • The behavior described in step 5 is a change from Mac OS 10.2, in which the instance variable search order was <key>, _<key>.
  • For backward binary compatibility, -handleQueryWithUnboundKey: will be invoked instead of -valueForUndefinedKey: in step 6, if the implementation of -handleQueryWithUnboundKey: in the receiver’s class is not NSObject’s.

简单翻译如下:

  1. 按照-get<Key>, -<key>, -is<Key>的顺序搜索该类的存取器方法,若找到,则直接调用。如果方法调用的结果是id类型,直接把结果返回。如果方法调用的结果是能够被NSNumber转换的标量类型,则结果会被转为NSNumber返回。否则对于一般的标量类型,这些类型将会被转化为NSValue(在Mac OS 10.5及以后,不仅仅支持NSPoint, NRange, NSRect,以及NSSize这些类型)。
  2. 如果简单的存取器方法没有找到,那么搜索该类的-countOf<Key>, -indexIn<Key>OfObject:方法,还有-objectIn<Key>AtIndex:(对应被NSOrderedSet类所定义的方法),-<key>AtIndexes:(对应-[NSOrderedSet objectsAtIndexes:])方法。如果-countOf<Key>, -indexIn<Key>OfObject:这两个方法被找到,另外两个方法中的至少一个被找到,那么这个能响应NSOrderedSet所有方法的集合代理对象会被返回。发送给原来消息接收者的-valueForKey:消息,将会被这个集合代理对象的-countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:, -<key>AtIndexes:这些方法共同处理。如果这个代理对象也实现了可选的-get<Key>:range:方法,这将有助于增强性能。
  3. 如果存取器方法和ordered set的代理方法没有被找到,那么搜索该类的-countOf<Key>方法,还有-objectIn<Key>AtIndex:(对应被NSArray类所定义的方法),-<key>AtIndexes:(对应-[NSArray objectsAtIndexes:])方法。如果-countOf<Key>这个方法被找到,另外两个方法中的至少一个被找到,那么这个能响应NSArray所有方法的集合代理对象会被返回。发送给原来消息接收者的-valueForKey:消息,将会被这个集合代理对象的-countOf<Key>, -objectIn<Key>AtIndex:, -<key>AtIndexes:这些方法共同处理。如果这个代理对象也实现了可选的-get<Key>:range:方法,这将有助于增强性能。
  4. 如果存取器方法、ordered setarray的代理方法都没有被找到,那么尝试搜索-countOf<Key>, -enumeratorOf<Key>, -memberOf<Key>:这些(被NSSet类所定义的)方法。如果这三个方法都能被找到,那么这个能响应NSSet所有方法的集合代理对象会被返回。发送给原来消息接收者的-valueForKey:消息,将会被这个集合代理对象的countOf<Key>, -enumeratorOf<Key>, -memberOf<Key>:这些方法共同处理。
  5. 如果存取器方法、ordered setarray以及set的代理方法都没有被找到,倘若此时消息接收者的+accessInstanceVariablesDirectly属性返回的是YES(默认实现就是返回YES),那么按照_<key>, _is<Key>, <key>, is<Key>的顺序搜索该类的实例变量。如果找到这个实例变量,那么按照步骤1中的类型转换规则返回这个实例变量的值。
  6. 否则(啥也没找到),调用-valueForUndefinedKey:方法并返回结果。这个方法的默认实现是抛出NSUndefinedKeyException异常,不过你可以重写该方法自行实现。

兼容性:

  • 为了向后兼容,会在步骤1中查找名称为-_get<Key>, -_<key>的存取器方法。如果找到了,会进行调用并按照步骤1中的类型转换规则返回调用的结果。

  • 从Mac OS 10.2开始,步骤5中的实例变量搜索顺序从原先的<key>, _<key>改为现在的_<key>, _is<Key>, <key>, is<Key>

  • 如果-handleQueryWithUnboundKey:的实现不是NSObject的默认实现(换句话说,自己手动实现了-handleQueryWithUnboundKey:方法),那在步骤6中,-handleQueryWithUnboundKey:方法将会代替-valueForUndefinedKey:方法被调用。

说得清晰明了。流程图如下:

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (id)valueForKey:(NSString *)key {
if(key) {
OSSpinLockLock(&NSKeyValueCachedAccessorSpinLock);
// 创建缓存getter的CFSet集合NSKeyValueCachedGetters
if(!NSKeyValueCachedGetters) {
// CFSet对象需要的结构体参数, 告诉这个集合怎么管理容器中的对象
// retain\release是内存管理 equal\hash是对象处理 copyDescription是复制处理
CFSetCallBacks callbacks = {0};
callbacks.version = kCFTypeSetCallBacks.version;
// 也可以禁用掉retain和release, 这样当对象销毁时需要及时将其从集合中移除否则会崩溃
// callbacks.retain = NULL;
// callbacks.release = NULL;
callbacks.retain = kCFTypeSetCallBacks.retain;
callbacks.release = kCFTypeSetCallBacks.release;
callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription;
callbacks.equal = (CFSetEqualCallBack)NSKeyValueAccessorIsEqual;
callbacks.hash = (CFSetHashCallBack)NSKeyValueAccessorHash;
NSKeyValueCachedGetters = CFSetCreateMutable(NULL,0,&callbacks);
}
// 根据class、key和hash创建唯一的NSKeyValueGetter对象, 作为从缓存集合中查找的"引子"
// 只要hashValue一致, 不管其他属性是否一致, 就可以判定这两个对象是一致的
// 这也是为啥首先根据class和key, 创建一个"简单"的finder到缓存集合中查找的原因
NSKeyValueGetter *finder = [NSKeyValueGetter new];
finder.containerClassID = object_getClass(self);
finder.key = key;
finder.hashValue = CFHash(key) ^ (NSUInteger)(object_getClass(self));
// 缓存集合中是否含有特定的NSKeyValueGetter
NSKeyValueGetter *getter = CFSetGetValue(NSKeyValueCachedGetters, finder);
if (!getter) {
// 缓存中没有找到, 创建getter
getter = [object_getClass(self) _createValueGetterWithContainerClassID:object_getClass(self) key:key];
// 这里的getter相比上面的finder更加具体详细, 虽然根据哈希来说, 二者是"相同的对象"
// 创建好getter后, 把它放到缓存集合中
CFSetAddValue(NSKeyValueCachedGetters, getter);
}
OSSpinLockUnlock(&NSKeyValueCachedAccessorSpinLock);
// 找到getter, 交给_NSGetUsingKeyValueGetter函数处理
return _NSGetUsingKeyValueGetter(self, getter);
}
else {
// key为空, 抛出异常
[NSException raise:NSInvalidArgumentException format:@"%@: attempt to retrieve a value for a nil key",_NSMethodExceptionProem(self,_cmd)];
}
return nil;
}

这个方法主要做了四件事:

  1. 取值时,使用OSSpinLockLock保证线程安全

  2. 根据class和key,生成一个NSKeyValueGetter对象,用于封装信息

  3. 取值时,会根据class和key配置一个简单的Getter,首先到CFSet缓存集合中进行查找,以提高查找速度

  4. key不存在时,直接抛出异常: 参数有误

方法二

当缓存集合中不存在时,便进入了更为具体的“查找”流程中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// 详细的查找流程(此处假定key为"name")
+ (NSKeyValueGetter *)_createValueGetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
NSKeyValueGetter * getter = nil;

// 获取字节长度
NSUInteger keyLen = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
// 这个数组用于存放首字符大写的key, 例如: "Name"
char keyCStrUpFirst[keyLen + 1];
// 将key转为C字符串, 存储在keyCStrUpFirst数组中
[key getCString:keyCStrUpFirst maxLength:keyLen + 1 encoding:NSUTF8StringEncoding];
if (key.length) {
// 将小写字母转为大写字母
keyCStrUpFirst[0] = toupper(keyCStrUpFirst[0]);
}
// 这个数组用于存放与key一致的字符, 例如: "name"
char keyCStr[keyLen + 16];
// 再将key转为C字符串, 存储在keyCStr数组中
[key getCString:keyCStr maxLength:keyLen + 1 encoding:NSUTF8StringEncoding];

Method getMethod = NULL;
// 查询方法指针, 使用'逻辑或'固定了默认顺序:getName==>name==>isName==>_getName==>_name
// 此处证明了接口文档中的第一步
// 1. Whose name matches the pattern -get<Key>, -<key>, or -is<Key>, in that order.
// 2. For backward binary compatibility, an accessor method whose name matches the pattern -_get<Key>, or -_<key> is searched for between steps 1 and 3.
if((getMethod = NSKeyValueMethodForPattern(self,"get%s",keyCStrUpFirst)) ||
(getMethod = NSKeyValueMethodForPattern(self,"%s",keyCStr)) ||
(getMethod = NSKeyValueMethodForPattern(self,"is%s",keyCStrUpFirst)) ||
(getMethod = NSKeyValueMethodForPattern(self,"_get%s",keyCStrUpFirst)) ||
(getMethod = NSKeyValueMethodForPattern(self,"_%s",keyCStr))) {
// 成功找到, 创建NSKeyValueMethodGetter对象, 保存找到的method
getter = [[NSKeyValueMethodGetter alloc] initWithContainerClassID:containerClassID key:key method:getMethod];
}
else {
/*
没有找到, 进入下个流程, 假定key为"name", 则:
ountOf_Method 对应 countOfName
ObjectIn_AtIndexMethod 对应 objectInNameAtIndex:
_AtIndexesMethod 对应 nameAtIndexes:
IndexIn_OfObjectMethod 对应 indexInNameOfObject:
enumeratorOf_Method 对应 enumeratorOfName
memberOf_Method 对应 memberOfName:
*/
Method ountOf_Method = NSKeyValueMethodForPattern(self, "countOf%", keyCStrUpFirst);
Method ObjectIn_AtIndexMethod = NSKeyValueMethodForPattern(self, "objectIn%sAtIndex:", keyCStrUpFirst);
Method _AtIndexesMethod = NSKeyValueMethodForPattern(self, "%sAtIndexes:", keyCStr);
Method IndexIn_OfObjectMethod = NSKeyValueMethodForPattern(self, "indexIn%sOfObject:", keyCStrUpFirst);

Method enumeratorOf_Method = NSKeyValueMethodForPattern(self, "enumeratorOf%s", keyCStrUpFirst);
Method memberOf_Method = NSKeyValueMethodForPattern(self, "memberOf%s:", keyCStrUpFirst);
if(ountOf_Method && IndexIn_OfObjectMethod && (ObjectIn_AtIndexMethod || _AtIndexesMethod)) {
// 第二步, 针对NSOrderedSet, ountOf_Method、IndexIn_OfObjectMethod,以及ObjectIn_AtIndexMethod或者_AtIndexesMethod中的一个存在(实现代理集合对象)
NSKeyValueNonmutatingOrderedSetMethodSet *methodSet = [[NSKeyValueNonmutatingOrderedSetMethodSet alloc] init];
methodSet.count = ountOf_Method;
methodSet.objectAtIndex = ObjectIn_AtIndexMethod;
methodSet.indexOfObject = IndexIn_OfObjectMethod;
methodSet.objectsAtIndexes = _AtIndexesMethod;
// eg: getName:range:方法(用于增强性能)
methodSet.getObjectsRange = NSKeyValueMethodForPattern(self, "get%s:range:", keyCStrUpFirst);
// NSKeyValueNonmutatingOrderedSetMethodSet就是一个拥有几个属性的简单的对象, 用于保存count、objectAtIndex等方法指针信息
// 成功找到, 创建NSKeyValueCollectionGetter对象, 保存保存好的methodSet对象
getter = [[NSKeyValueCollectionGetter alloc] initWithContainerClassID:containerClassID key:key methods:methodSet proxyClass:NSKeyValueOrderedSet.self];
[methodSet release];
}
else if(ountOf_Method && (ObjectIn_AtIndexMethod || _AtIndexesMethod)){
// 第三步, 针对NSArray, ountOf_Method、以及ObjectIn_AtIndexMethod或者_AtIndexesMethod中的一个存在
NSKeyValueNonmutatingArrayMethodSet *methodSet = [[NSKeyValueNonmutatingArrayMethodSet alloc] init];
methodSet.count = ountOf_Method;
methodSet.objectAtIndex = ObjectIn_AtIndexMethod;
methodSet.objectsAtIndexes = _AtIndexesMethod;
methodSet.getObjectsRange = NSKeyValueMethodForPattern(self, "get%s:range:", keyCStrUpFirst);
// 同样的, 成功找到, 创建NSKeyValueCollectionGetter对象,保存methodSet
getter = [[NSKeyValueCollectionGetter alloc] initWithContainerClassID:containerClassID key:key methods:methodSet proxyClass:NSKeyValueArray.self];
[methodSet release];
}
else if(ountOf_Method && enumeratorOf_Method && memberOf_Method){
// 第四步, 针对NSSet, ountOf_Method、enumeratorOf_Method以及memberOf_Method
NSKeyValueNonmutatingSetMethodSet *methodSet = [[NSKeyValueNonmutatingSetMethodSet alloc] init];
methodSet.count = ountOf_Method;
methodSet.enumerator = enumeratorOf_Method;
methodSet.member = memberOf_Method;
getter = [[NSKeyValueCollectionGetter alloc] initWithContainerClassID:containerClassID key:key methods:methodSet proxyClass:NSKeyValueSet.self];
[methodSet release];
}
else if([self accessInstanceVariablesDirectly]) {
// 第五步, 如果允许直接访问实例变量, 也即accessInstanceVariablesDirectly为YES, 则直接取出实例变量
// 默认顺序为_name==>_isName==>name==>isName
Ivar ivar = NULL;
if((ivar = NSKeyValueIvarForPattern(self, "_%s", keyCStr)) ||
(ivar = NSKeyValueIvarForPattern(self, "_is%s", keyCStrUpFirst)) ||
(ivar = NSKeyValueIvarForPattern(self, "%s", keyCStr)) ||
(ivar = NSKeyValueIvarForPattern(self, "is%s", keyCStrUpFirst))
) {
// 此时ivar有值, 创建NSKeyValueIvarGetter
getter = [[NSKeyValueIvarGetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}
}

if(!getter) {
// 最后, getter创建失败, 说明方法、实例变量查询失败, 进入下个流程
getter = [self _createValuePrimitiveGetterWithContainerClassID:containerClassID key:key];
}

return getter;
}

这个方法详细地设定了查找的顺序,值得关注的是,NSKeyValueMethodForPattern()这个函数调用的次数相当的多。

方法三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//最后一次查找
+ (NSKeyValueGetter *)_createValuePrimitiveGetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
NSKeyValueGetter *getter = nil;
NSUInteger keyCstrLen = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char keyCstrUpFirst[keyCstrLen + 1];
[key getCString:keyCstrUpFirst maxLength:keyCstrLen + 1 encoding:NSUTF8StringEncoding];
if(key.length) {
keyCstrUpFirst[0] = toupper(keyCstrUpFirst[0]);
}
char keyCstr[keyCstrLen + 1];
[key getCString:keyCstr maxLength:keyCstrLen + 1 encoding:NSUTF8StringEncoding];
Method getMethod = NULL;
// 同样的套路, 查找顺序: getPrimitiveName==>primitiveName
if((getMethod = NSKeyValueMethodForPattern(self, "getPrimitive%s", keyCstrUpFirst)) ||
(getMethod = NSKeyValueMethodForPattern(self, "primitive%s", keyCstrUpFirst))
) {
getter = [[NSKeyValueMethodGetter alloc] initWithContainerClassID:containerClassID key:key method:getMethod];
}
else if([self accessInstanceVariablesDirectly]) {
Ivar ivar = NULL;
// 直接访问实例变量
// 在方法二中找过一遍了, 为啥还要再找一遍?
// 说明要么是根本没有找到, 要么是虽然找到了, 但是在创建NSKeyValueIvarGetter或者NSKeyValueMethodGetter的时候失败了, 最终的getter还是nil
if ((ivar = NSKeyValueIvarForPattern(self, "_%s", keyCstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "_is%s", keyCstrUpFirst)) ||
(ivar = NSKeyValueIvarForPattern(self, "%s", keyCstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "is%s", keyCstrUpFirst))
) {
getter = [[NSKeyValueIvarGetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}

if(!getter) {
getter = [self _createOtherValueGetterWithContainerClassID:containerClassID key:key];
}

return getter;
}

方法四

1
2
3
4
// 转发处理给NSKeyValueUndefinedGetter对象
+ (id)_createOtherValueGetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
return [[NSKeyValueUndefinedGetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self];
}

方法五

1
2
3
4
5
6
7
8
9
// NSKeyValueUndefinedGetter负责调用其父类(NSKeyValueGetter)的构造方法
@implementation NSKeyValueUndefinedGetter
- (id)initWithContainerClassID:(id)containerClassID key:(NSString *)key containerIsa:(Class)containerIsa {
void *arguments[3] = {0};
arguments[0] = key;
// 调用valueForUndefinedKey方法, 该方法默认实现抛出异常
return [super initWithContainerClassID:containerClassID key:key implementation:methogetImplementation(class_getInstanceMethod(containerIsa,@selector(valueForUndefinedKey:))) selector:@selector(valueForUndefinedKey:) extraArguments:arguments count:1];
}
@end

方法怎么查找

1
2
3
4
5
6
7
8
9
// NSKeyValueMethodForPattern
Method NSKeyValueMethodForPattern(Class class, const char *pattern,const char *param) {
size_t paramLen = strlen(param);
size_t patternLen = strlen(pattern);
char selName[patternLen + paramLen * 2 + 1];
snprintf(selName, (patternLen + paramLen * 2 + 1), pattern,param,param);
// 依赖Runtime的class_getInstanceMethod
return class_getInstanceMethod(class, sel_registerName(selName));
}

实例变量怎么查找

1
2
3
4
5
6
7
8
9
// NSKeyValueIvarForPattern
Ivar NSKeyValueIvarForPattern(Class class, const char *pattern,const char *param) {
size_t paramLen = strlen(param);
size_t patternLen = strlen(pattern);
char ivarName[paramLen + patternLen + 1];
snprintf(ivarName, paramLen + patternLen + 1, pattern,param);
// 依赖Runtime的class_getInstanceVariable
return class_getInstanceVariable(class, ivarName);
}

NSKeyValueMethodGetter如何创建

1. NSKeyValueMethodGetter构造方法中生成IMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@implementation NSKeyValueMethodGetter
- (id)initWithContainerClassID:(id)containerClassID key:(NSString *)key method:(Method)method {
NSUInteger methodArgumentsCount = methogetNumberOfArguments(method);
NSUInteger extraAtgumentCount = 1;
// 默认两个参数((void (*)(id, SEL))objc_msgSend)
if(methodArgumentsCount == 2) {
char *returnType = methocopyReturnType(method);
IMP imp = NULL;
switch (returnType[0]) {
case '#':
case '@': {
// 返回类型是对象时, 直接获取method的函数指针
// 还是以key为"name"举例, 则方法为- (NSString *)name; 这里直接获取其IMP
imp = methogetImplementation(method);
extraAtgumentCount = 0;
} break;
case 'B': {
/*
// 获取将BOOL类型转为NSNumber对象类型的函数指针
NSNumber * _NSGetBoolValueWithMethod(id object, SEL selctor, Method method) {
return [[[NSNumber alloc] initWithBool: ((BOOL (*)(id,SEL))methogetImplementation(method))(object, methogetName(method))] autorelease];
}
// 比如key为"isMan", 则方法为 - (BOOL)iaMan;
// 获取的IMP便是将普通BOOL类型值转化为NSNumber类型对象的函数指针, 其他类似
*/
imp = (IMP)_NSGetBoolValueWithMethod;
} break;
case 'C': {imp = (IMP)_NSGetUnsignedCharValueWithMethod;} break;
case 'I': {imp = (IMP)_NSGetUnsignedIntValueWithMethod;} break;
case 'Q': {imp = (IMP)_NSGetUnsignedLongLongValueWithMethod;} break;
case 'L': {imp = (IMP)_NSGetUnsignedLongValueWithMethod;} break;
case 'S': {imp = (IMP)_NSGetUnsignedShortValueWithMethod;} break;
case 'c': {imp = (IMP)_NSGetCharValueWithMethod;} break;
case 'd': {imp = (IMP)_NSGetDoubleValueWithMethod;} break;
case 'f': {imp = (IMP)_NSGetFloatValueWithMethod;} break;
case 'i': {imp = (IMP)_NSGetIntValueWithMethod;} break;
case 'l': {imp = (IMP)_NSGetLongValueWithMethod;} break;
case 'q': {imp = (IMP)_NSGetLongLongValueWithMethod;} break;
case 's': {imp = (IMP)_NSGetShortValueWithMethod;} break;
case '{': {
if (strcmp(returnType, @encode(CGPoint)) == 0){
imp = (IMP)_NSGetPointValueWithMethod;
}
else if (strcmp(returnType, @encode(NSRange)) == 0){
imp = (IMP)_NSGetRangeValueWithMethod;
}
else if (strcmp(returnType, @encode(CGRect)) == 0){
imp = (IMP)_NSGetRectValueWithMethod;
}
else if (strcmp(returnType, @encode(CGSize)) == 0){
imp = (IMP)_NSGetSizeValueWithMethod;
}
else {
imp = (IMP)_NSGetValueWithMethod;
}
} break;
}

free(returnType);
if(imp) {
void *arguments[3] = {0};
if(extraAtgumentCount > 0) {
arguments[0] = method;
}
// 将class key selector imp method 参数 参数数量等信息交给父类处理
return [super initWithContainerClassID:containerClassID key:key implementation:imp selector:methogetName(method) extraArguments:arguments count:extraAtgumentCount];
}
else {
[self release];
return nil;
}
}
else {
[self release];
return nil;
}
}
@end

由于KVC返回的类型为对象(NSObject),所以需要对方法返回值类型分别进行判断从而为Getter赋值不同的函数指针。也即从BOOL、double、int、CGSize等普通类型转化为NSNumber、NSValue、id等对象类型的函数(指针)。

2. 在父类NSKeyValueAccessor中, 对class key selector imp method 参数 参数数量等信息进行保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@implementation NSKeyValueAccessor
- (id)initWithContainerClassID:(id)containerClassID key:(NSString *)key implementation:(IMP)implementation selector:(SEL)selector extraArguments:(void *[3])extraArguments count:(NSUInteger)count {
if (self = [super init]) {
_containerClassID = containerClassID;
_key = key.copy;
_implementation = implementation;
_selector = selector;

NSUInteger hash = 0;
if (key) {
hash = CFHash(key);
}
hash ^= (NSUInteger)containerClassID;
_hashValue = hash;
_extraArgumentCount = count;
_extraArgument1 = extraArguments[0];
if (_extraArgument1 == key) {
_extraArgument1 = _key;
}
_extraArgument2 = extraArguments[1];
if (_extraArgument2 == key) {
_extraArgument2 = _key;
}
_extraArgument3 = extraArguments[2];
}
return self;
}
@end

NSKeyValueIvarGetter如何创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- (id)initWithContainerClassID:(id)containerClassID key:(NSString *)key containerIsa:(Class)containerIsa ivar:(Ivar)ivar {
const char *ivarEncoding = ivar_getTypeEncoding(ivar);
IMP imp = NULL;
switch (ivarEncoding[0]) {
case '#':
case '@': {
objc_ivar_memory_management_t mngment = objc_ivar_memoryUnknown;//_class_getIvarMemoryManagement(containerIsa, ivar);
if(mngment < objc_ivar_memoryWeak) {
/*
id _NSGetObjectGetAssignValueInIvar(id object, SEL selector, Ivar ivar) {
return *(id *)object_getIvarAddress(object, ivar);
}
*/
imp = (IMP)_NSGetObjectGetAssignValueInIvar;
}
else if (mngment == objc_ivar_memoryWeak) {
/*
id _NSGetObjectGetWeakValueInIvar(id object, SEL selector, Ivar ivar) {
return objc_loadWeak((id *)object_getIvarAddress(object, ivar));
}
*/
imp = (IMP)_NSGetObjectGetWeakValueInIvar;
}
else if(mngment == objc_ivar_memoryUnretained) {
imp = (IMP)_NSGetObjectGetAssignValueInIvar;
}
else {
imp = (IMP)_NSGetObjectGetIvarValueInIvar;
}
}
break;
case 'C': {
/*
NSNumber * _NSGetUnsignedCharValueInIvar(id object, SEL selector, Ivar ivar) {
unsigned char value = *(unsigned char *)object_getIvarAddress(object, ivar);
return [[[NSNumber alloc] initWithUnsignedChar:value] autorelease];
}
*/
imp = (IMP)_NSGetUnsignedCharValueInIvar;
}
break;
case 'B': {imp = (IMP)_NSGetBoolValueInIvar;}break;
case 'I': {imp = (IMP)_NSGetUnsignedIntValueInIvar;}break;
case 'L': {imp = (IMP)_NSGetUnsignedLongValueInIvar;}break;
case 'Q': {imp = (IMP)_NSGetUnsignedLongLongValueInIvar;}break;
case 'S': {imp = (IMP)_NSGetUnsignedShortValueInIvar;} break;
case '{': {
char* idx = index(ivarEncoding, '=');
if (idx == NULL) {
imp = (IMP)_NSGetValueInIvar;
}
else if (strncmp(ivarEncoding, @encode(CGPoint), idx - ivarEncoding) == 0){
imp = (IMP)_NSGetPointValueInIvar;
}
else if (strncmp(ivarEncoding, @encode(NSRange), idx - ivarEncoding) == 0){
imp = (IMP)_NSGetRangeValueInIvar;
}
else if (strncmp(ivarEncoding, @encode(CGRect), idx - ivarEncoding) == 0){
imp = (IMP)_NSGetRectValueInIvar;
}
else if (strncmp(ivarEncoding, @encode(CGSize), idx - ivarEncoding) == 0){
imp = (IMP)_NSGetSizeValueInIvar;
}
else {
imp = (IMP)_NSGetValueInIvar;
}
}
break;
case 'c': {imp = (IMP)_NSGetCharValueInIvar;}break;
case 'd': {imp = (IMP)_NSGetDoubleValueInIvar;}break;
case 'f': {imp = (IMP)_NSGetFloatValueInIvar;}break;
case 'i': {imp = (IMP)_NSGetIntValueInIvar;}break;
case 'l': {imp = (IMP)_NSGetLongValueInIvar;}break;
case 'q': {imp = (IMP)_NSGetLongLongValueInIvar;}break;
case 's': {imp = (IMP)_NSGetShortValueInIvar;}break;
}

if(imp) {
void *arguments[3] = {0};
arguments[0] = ivar;
return [super initWithContainerClassID:containerClassID key:key implementation:imp selector:NULL extraArguments:arguments count:1];
} else {
[self release];
return nil;
}
}

同样地,判断实例变量的类型编码,进而赋值不同的IMP。

怎么根据Getter取值

1. 线程校验

1
2
3
4
5
void NSKeyValueObservingAssertRegistrationLockNotHeld() {
if(_NSKeyValueObserverRegistrationEnableLockingAssertions && _NSKeyValueObserverRegistrationLockOwner == pthreaself()) {
assert(pthreaself() != _NSKeyValueObserverRegistrationLockOwner);
}
}

2. 直接调用Getter中存储的方法实现(getter.implementation)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
id _NSGetUsingKeyValueGetter(id object, NSKeyValueGetter *getter) {
// 线程判断
NSKeyValueObservingAssertRegistrationLockNotHeld();
// 根据其他参数数量extraArgumentCount分别进行函数调用
switch (getter.extraArgumentCount) {
case 0: {
return ( (id (*)(id,SEL))getter.implementation )(object,getter.selector);
} break;
case 1: {
return ( (id (*)(id,SEL,void*))getter.implementation )(object,getter.selector, getter.extraArgument1);
} break;
case 2: {
return ( (id (*)(id,SEL,void*,void*))getter.implementation )(object,getter.selector, getter.extraArgument1, getter.extraArgument2);
} break;
case 3: {
return ( (id (*)(id,SEL,void*,void*,void*))getter.implementation )(object,getter.selector, getter.extraArgument1, getter.extraArgument2, getter.extraArgument3);
} break;
default: break;
}
return nil;
}

valueForKeyPath:

假定这里的keyPath为@"key1.key2.key3.key4"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- (id)valueForKeyPath:(NSString *)keyPath {
if(keyPath) {
// 字符串编码判断
CFStringEncoding encoding = __CFDefaultEightBitStringEncoding;
if(encoding == kCFStringEncodingInvalidId) {
// 编码无效 kCFStringEncodingInvalidId就是 (0xffffffffU)
encoding = __CFStringComputeEightBitStringEncoding();
}
// 创建C字符串: "key1.key2.key3.key4"
const char *cStr = CFStringGetCStringPtr((CFStringRef)keyPath, encoding);
if(cStr) {
// memchr函数:从头开始搜寻s 所指的内存内容前n 个字节,直到发现第一个值为c 的字节,则返回指向该字节的指针
// 所以最后获取到包含'点符号'的后部分 即firstDotPointers为 ".key2.key3.key4"
const char *firstDotPointer = memchr(cStr, '.', keyPath.length);
if(firstDotPointer) {
// 这里的subKey是 "key1"
NSString *subKey = [[keyPath substringWithRange:NSMakeRange(0, firstDotPointer - cStr)] retain];
// 这里的subKeyPathLeft是 "key2.key3.key4"
NSString *subKeyPathLeft = [[keyPath substringWithRange:NSMakeRange(firstDotPointer - cStr + 1, keyPath.length - (firstDotPointer - cStr + 1))] retain];
// 先获取到subKey的结果, 然后用它的结果再求subKeyPathLeft, 进入递归中
id value = [[self valueForKey:subKey] valueForKeyPath:subKeyPathLeft];
[subKey release];
[subKeyPathLeft release];
return value;
}
else {
// firstDotPointer不存在, 说明keyPath中没有'点符号', 则直接调用valueForKey
return [self valueForKey:keyPath];
}
}
}
// 走到这里, 上面代码没有return, 说明keyPath为nil或者cStr为nil
NSRange range = [keyPath rangeOfString:@"." options:NSLiteralSearch range:NSMakeRange(0, keyPath.length)];
if(range.length) {
// range.length不为0, 也即keyPath中有'点符号'
// subKey为"key1"
NSString *subKey = [[keyPath substringWithRange:NSMakeRange(0, range.location)] retain];
// subKeyPathLeft为"key2.key3.key4"
NSString *subKeyPathLeft = [[keyPath substringWithRange:NSMakeRange(range.location + 1, keyPath.length - (range.location + 1))] retain];
// 同样的, 先获取到subKey的结果, 然后用它的结果再求subKeyPathLeft, 进入递归中
id value = [[self valueForKey:subKey] valueForKeyPath:subKeyPathLeft];
[subKey release];
[subKeyPathLeft release];
return value;
}
else {
// keyPath为nil或者keyPath中没有'点符号', 直接调用valueForKey
return [self valueForKey:keyPath];
}
}

这里就有一个问题了,相似的把keyPath拆分逻辑的逻辑为啥要写两个,一个转为C字符串拆分,一个直接拆分? 揣测两者的区别主要是对字符串编码的判断。

三、设值

设值的流程就比较简单了。

The default implementation of this method does the following:

  1. Searches the class of the receiver for an accessor method whose name matches the pattern -set<Key>:. If such a method is found the type of its parameter is checked. If the parameter type is not an object pointer type but the value is nil -setNilValueForKey: is invoked. The default implementation of -setNilValueForKey: raises an NSInvalidArgumentException, but you can override it in your application. Otherwise, if the type of the method’s parameter is an object pointer type the method is simply invoked with the value as the argument. If the type of the method’s parameter is some other type the inverse of the NSNumber/NSValue conversion done by -valueForKey: is performed before the method is invoked.

  2. Otherwise (no accessor method is found), if the receiver’s class’ +accessInstanceVariablesDirectly property returns YES, searches the class of the receiver for an instance variable whose name matches the pattern _<key>, _is<Key>, <key>, or is<Key>, in that order. If such an instance variable is found and its type is an object pointer type the value is retained and the result is set in the instance variable, after the instance variable’s old value is first released. If the instance variable’s type is some other type its value is set after the same sort of conversion from NSNumber or NSValue as in step 1.

  3. Otherwise (no accessor method or instance variable is found), invokes -setValue:forUndefinedKey:. The default implementation of -setValue:forUndefinedKey: raises an NSUndefinedKeyException, but you can override it in your application.

Compatibility notes:

  • For backward binary compatibility with -takeValue:forKey:‘s behavior, a method whose name matches the pattern -_set<Key>: is also recognized in step 1. KVC accessor methods whose names start with underscores were deprecated as of Mac OS 10.3 though.
  • For backward binary compatibility, -unableToSetNilForKey: will be invoked instead of -setNilValueForKey: in step 1, if the implementation of -unableToSetNilForKey: in the receiver’s class is not NSObject’s.
  • The behavior described in step 2 is different from -takeValue:forKey:‘s, in which the instance variable search order is <key>, _<key>.
  • For backward binary compatibility with -takeValue:forKey:‘s behavior, -handleTakeValue:forUnboundKey: will be invoked instead of -setValue:forUndefinedKey: in step 3, if the implementation of -handleTakeValue:forUnboundKey: in the receiver’s class is not NSObject’s.

翻译如下:
这个方法的默认实现是这样的:

  1. 搜索该类名称为-set<Key>:的存取器方法,如果找到,检查其参数类型。如果参数为nil-setNilValueForKey:方法将会被调用。这个方法的默认实现是抛出NSInvalidArgumentException异常,不过你可以重写该方法自行实现。如果参数类型为对象类型,该存取器方法会被直接调用,这个参数也会被直接使用。如果参数能被转化为NSNumber/NSValue类型,参数会在存取器方法被调用之前进行转换。
  2. 如果存取器方法没有被找到,倘若此时消息接收者的+accessInstanceVariablesDirectly属性返回的是YES,那么按照_<key>, _is<Key>, <key>, is<Key>的顺序搜索该类的实例变量。如果找到这个实例变量,当其为对象类型时,该实例变量会在旧值释放之后被设置新值。当其为其他类型时,那么按照步骤1中的类型转换规则设置这个实例变量的值。
  3. 如果存取器方法和实例变量都没有被找到,-setValue:forUndefinedKey:方法将会被调用。这个方法的默认实现是抛出NSUndefinedKeyException异常,不过你可以重写该方法自行实现。

兼容性:

  • 为了向后兼容-takeValue:forKey:,名称为-_set<Key>:的方法也会在步骤1中被查找。
  • 如果-unableToSetNilForKey:的实现不是NSObject的默认实现(换句话说,自己手动实现了-unableToSetNilForKey:方法),那在步骤1中,-unableToSetNilForKey:方法将会代替-setNilValueForKey:方法被调用。
  • 对于-takeValue:forKey:,其实例变量的查找顺序不同于步骤2所描述的,调用它时,实例变量查找顺序是<key>, _<key>
  • 为了向后兼容-takeValue:forKey:,如果-handleTakeValue:forUnboundKey:的实现不是NSObject的默认实现(换句话说,自己手动实现了-handleTakeValue:forUnboundKey:方法),那在步骤3中,-handleTakeValue:forUnboundKey:方法将会代替-setValue:forUndefinedKey:方法被调用。

流程图如下:

setValue:

方法一

这里使用NSKeyValueCachedSetters缓存setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 假定key为@"name"
- (void)setValue:(id)value forKey:(NSString *)key {
if (key) {
// 加锁
OSSpinLockLock(&NSKeyValueCachedAccessorSpinLock);
if (!NSKeyValueCachedSetters) {
CFSetCallBacks callbacks = {0};
callbacks.version = kCFTypeSetCallBacks.version;
callbacks.retain = kCFTypeSetCallBacks.retain;
callbacks.release = kCFTypeSetCallBacks.release;
callbacks.copyDescription = kCFTypeSetCallBacks.copyDescription;
callbacks.equal = (CFSetEqualCallBack)NSKeyValueAccessorIsEqual;
callbacks.hash = (CFSetHashCallBack)NSKeyValueAccessorHash;
NSKeyValueCachedSetters = CFSetCreateMutable(NULL,0,&callbacks);
}
NSKeyValueSetter *finder = [NSKeyValueSetter new];
finder.containerClassID = object_getClass(self);
finder.key = key;
finder.hashValue = CFHash((CFTypeRef)key) ^ (NSUInteger)(object_getClass(self));
// 缓存中取Setter
NSKeyValueSetter *setter = CFSetGetValue(NSKeyValueCachedSetters, (void *)finder);
if (!setter) {
setter = [object_getClass(self) _createValueSetterWithContainerClassID:object_getClass(self) key:key];
CFSetAddValue(NSKeyValueCachedSetters, (void*)setter);
}
// 解锁
OSSpinLockUnlock(&NSKeyValueCachedAccessorSpinLock);
// 设值
_NSSetUsingKeyValueSetter(self,setter, value);
}
else {
[NSException raise:NSInvalidArgumentException format:@"%@: attempt to set a value for a nil key",_NSMethodExceptionProem(self,_cmd)];
}
}

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
+ (NSKeyValueSetter *)_createValueSetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
NSKeyValueSetter *setter = nil;
NSUInteger key_cstr_len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
// 首字符大写的key
char key_cstr_upfirst[key_cstr_len + 1];
[key getCString:key_cstr_upfirst maxLength:key_cstr_len + 1 encoding:NSUTF8StringEncoding];
if (key.length) {
key_cstr_upfirst[0] = toupper(key_cstr_upfirst[0]);
}
// 原来的key
char key_cstr[key_cstr_len + 1];
[key getCString:key_cstr maxLength:key_cstr_len + 1 encoding:NSUTF8StringEncoding];

Method method = NULL;
// 查找方法顺序, 假定key为@"name"
// setName: ==> _setName: ==> setIsName:
if ((method = NSKeyValueMethodForPattern(self, "set%s:", key_cstr_upfirst)) ||
(method = NSKeyValueMethodForPattern(self, "_set%s:", key_cstr_upfirst)) ||
(method = NSKeyValueMethodForPattern(self, "setIs%s:", key_cstr_upfirst))
) {
setter = [[NSKeyValueMethodSetter alloc] initWithContainerClassID:containerClassID key:key method:method];
}
else if ([self accessInstanceVariablesDirectly]) {
Ivar ivar = NULL;
// 允许直接访问实例变量, 查找顺序为: _name ==> _isName ==> name ==> isName
if ((ivar = NSKeyValueIvarForPattern(self, "_%s", key_cstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "_is%s", key_cstr_upfirst)) ||
(ivar = NSKeyValueIvarForPattern(self, "%s", key_cstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "is%s", key_cstr_upfirst))
) {
setter = [[NSKeyValueIvarSetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}
if (!setter) {
setter = [self _createValuePrimitiveSetterWithContainerClassID:containerClassID key:key];
}
return setter;
}

方法三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
+ (NSKeyValueSetter *)_createValuePrimitiveSetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
NSKeyValueSetter *setter = nil;
NSUInteger keyCstrLen = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char keyCstrUpFirst[keyCstrLen + 1];

[key getCString:keyCstrUpFirst maxLength:keyCstrLen + 1 encoding:NSUTF8StringEncoding];

if(key.length) {
keyCstrUpFirst[0] = toupper(keyCstrUpFirst[0]);
}

char keyCstr[keyCstrLen + 1];
[key getCString:keyCstr maxLength:keyCstrLen + 1 encoding:NSUTF8StringEncoding];
// 假定key为@"name", 查询 setPrimitiveName:方法
Method method = NSKeyValueMethodForPattern(self,"setPrimitive%s:",keyCstrUpFirst);
if(method) {
setter = [[NSKeyValueMethodSetter alloc] initWithContainerClassID:containerClassID key:key method:method];
}
else {
if([self accessInstanceVariablesDirectly]) {
Ivar ivar = NULL;
if ((ivar = NSKeyValueIvarForPattern(self, "_%s", keyCstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "_is%s", keyCstrUpFirst)) ||
(ivar = NSKeyValueIvarForPattern(self, "%s", keyCstr)) ||
(ivar = NSKeyValueIvarForPattern(self, "is%s", keyCstrUpFirst))
) {
setter = [[NSKeyValueIvarSetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self ivar:ivar];
}
}
}

if(!setter) {
setter = [self _createOtherValueSetterWithContainerClassID:containerClassID key:key];
}

return setter;
}

方法四

1
2
3
+ (NSKeyValueSetter *)_createOtherValueSetterWithContainerClassID:(id)containerClassID key:(NSString *)key {
return [[NSKeyValueUndefinedSetter alloc] initWithContainerClassID:containerClassID key:key containerIsa:self];
}

怎么根据Setter设值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void _NSSetUsingKeyValueSetter(id object, NSKeyValueSetter *setter, id value) {
switch (setter.extraArgumentCount) {
case 0: {
( (id (*)(id,SEL,id))setter.implementation )(object,setter.selector,value);
}
break;
case 1: {
( (id (*)(id,SEL,id,void*))setter.implementation )(object,setter.selector, value, setter.extraArgument1);
}
break;
case 2: {
( (id (*)(id,SEL,id,void*,void*))setter.implementation )(object,setter.selector, value, setter.extraArgument1, setter.extraArgument2);
}
break;
case 3: {
( (id (*)(id,SEL,id,void*,void*,void*))setter.implementation )(object,setter.selector, value, setter.extraArgument1, setter.extraArgument2, setter.extraArgument3);
}
break;
default:
break;
}
}

直接调用Setter中存储的方法实现(getter.implementation)。

setValue:forKeyPath:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath {
if(keyPath) {
CFStringEncoding encoding = __CFDefaultEightBitStringEncoding;
if(encoding == kCFStringEncodingInvalidId) {
encoding = __CFStringComputeEightBitStringEncoding();
}
const char *keyPathCStr = CFStringGetCStringPtr((CFStringRef)keyPath, encoding);
if(keyPathCStr) {
const char *firstDotPointer = memchr(keyPathCStr, '.', keyPath.length);
if(firstDotPointer) {
NSString *subKey = [[keyPath substringWithRange:NSMakeRange(0, firstDotPointer - keyPathCStr)] retain];
NSString *subKeyPathAfterDot = [[keyPath substringWithRange:NSMakeRange(firstDotPointer - keyPathCStr + 1, keyPath.length - (firstDotPointer - keyPathCStr + 1))] retain];

[[self valueForKey:subKey] setValue:value forKeyPath:subKeyPathAfterDot];

[subKey release];
[subKeyPathAfterDot release];
}
else {
[self setValue:value forKey:keyPath];
}
}
}
NSRange dotRange = [keyPath rangeOfString:@"." options:NSLiteralSearch range:NSMakeRange(0, keyPath.length)];
if(dotRange.length) {
NSString *subKey = [[keyPath substringWithRange:NSMakeRange(0, dotRange.location)] retain];
NSString *subKeyPathAfterDot = [[keyPath substringWithRange:NSMakeRange(dotRange.location + 1, keyPath.length - (dotRange.location + 1))] retain];

[[self valueForKey:subKey] setValue:value forKeyPath:subKeyPathAfterDot];

[subKey release];
[subKeyPathAfterDot release];
}
else {
[self setValue:value forKey:keyPath];
}
}

基本是与取值类似的逻辑。

四、集合对象的KVC

NSArray的KVC

接口

1
2
3
4
5
6
7
8
@interface NSArray (NSKeyValueCoding)
// 返回Array内每个对象的“key”对应值组成的数组
- (id)valueForKey:(NSString *)key;
// 如果keyPath中包含集合运算符, 则返回运算结果, 否则返回Array内每个对象的“keyPath”对应值组成的数组
- (id)valueForKeyPath:(NSString *)keyPath;
// 设置Array里每个对象的key对应值为value
- (void)setValue:(id)value forKey:(NSString *)key;
@end

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
- (id)valueForKey:(NSString *)key {
NSString *operationKey = nil;
// 集合运算符:如@count, @firstObject, @lastObject等
if (key.length && [key characterAtIndex:0] == '@' && (operationKey = [key substringWithRange:NSMakeRange(1, key.length - 1)])) {
// 去掉'@'便是operationKey
id value = [super valueForKey:operationKey];
return value;
}
else {
// 创建与自身相等数量的array
id *objectsBuff = NSAllocateObjectArray(self.count);
// 现在, 指针p与指针objectsBuff指向一致
id *p = objectsBuff;
// 遍历自身
for (id object in self) {
// 取出容器中的元素对应的value
id eachValue = [object valueForKey:key];
// 如果eachValue不存在, 则p的next指针指向的对象设置成 NSNull实例对象
// 如果eachValue有值, 则p的next指针指向的对象设置为eachValue
*(p++) = (eachValue ? : [NSNull null]);
}
// 根据objectsBuff创建一个数组, 这个objectsBuff就是'eachValue'的集合
// 也即假定key为@"name", 遍历容器中所有元素, 取出每个元素key为@"name"对应的值, 这些值的集合就是数组arrayValue
NSArray *arrayValue = [[[NSArray alloc] initWithObjects:objectsBuff count:self.count] autorelease];
// 释放objectsBuff
NSFreeObjectArray(objectsBuff);
return arrayValue;
}
}

- (id)valueForKeyPath:(NSString *)keyPath {
// 集合运算符: 如@count, @firstObject, @"@unionOfObjects.friend.name"等
// 这里以 @"@unionOfObjects.friend"为例
if(keyPath.length && [keyPath characterAtIndex:0] == '@') {
// 说明keyPath中有'@符号', 且'@符号'在第0个位置处
NSRange dotRange = [keyPath rangeOfString:@"." options:NSLiteralSearch range:NSMakeRange(0, keyPath.length)];
if(dotRange.length) {
// dotRange.length不为0, 说明keyPath中有'@符号', 而且还有'点符号'
// 取出包含运算符的那部分,如 @"unionOfObjects"
NSString *operator = [keyPath substringWithRange:NSMakeRange(0, dotRange.location)];
// 取出除运算符之外的那部分,如 @"friend"
NSString *keyPathForOperator = [keyPath substringWithRange:NSMakeRange(dotRange.location + 1, keyPath.length - (dotRange.location + 1))];
if(keyPathForOperator) {
// 说明含运算符的那部分如 @"unionOfObjects" 和除运算符之外的那部分如 @"friend" 都存在
NSUInteger operatorCStrLength = [operator lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char operatorCStr[operatorCStrLength + 1];
// 转为C字符串operatorCStr, 也即 @"unionOfObjects" 转为 "unionOfObjects"
[operator getCString:operatorCStr maxLength:operatorCStrLength + 1 encoding:NSUTF8StringEncoding];
// 查找方法, 即: "unionOfObjectsForKeyPath:"
Method operatorMethod = NSKeyValueMethodForPattern(self.class, "%sForKeyPath:", operatorCStr);
if(!operatorMethod) {
// 上面的方法没找到, 就查找带下划线的那个: "_unionOfObjectsForKeyPath:"
operatorMethod = NSKeyValueMethodForPattern(self.class, "_%sForKeyPath:", operatorCStr);
}
if (operatorMethod) {
// 查找成功, 调用运算符对应的方法
id value = ((id (*)(id,Method,NSString *))methoinvoke)(self,operatorMethod,keyPathForOperator);
return value;
}
else {
// 没有找到, 说明是不支持的运算符
[NSException raise:NSInvalidArgumentException format:@"[<%@ %p> valueForKeyPath:]: this class does not implement the %@ operation.", self.class,self,operator];
return nil;
}
}
else {
// 说明只有包含运算符的那部分如 @"friend", 走NSObject的valueForKey逻辑
id value = [super valueForKey:operator];
return value;
}
}
else {
// keyPath中有'@符号', 但是没有'点符号', 取出除'@符号'之外的key
NSString *key = [[keyPath substringWithRange:NSMakeRange(1, keyPath.length - 1)] retain];
// 走NSObject的valueForKey逻辑
id value = [super valueForKey:key];
return value;
}
}
else {
// 没有'@符号',可能有'点符号', 走NSObject的valueForKeyPath逻辑
return [super valueForKeyPath: keyPath];
}
}

- (void)setValue:(id)value forKey:(NSString *)key {
for (id object in self) {
// 对容器内的每一个元素都设值
[object setValue:value forKey:key];
}
}

求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// @sum.keyPath, 例如 @"@sum.price", 传递到这个方法中, 参数keyPath为@"price"
- (NSNumber *)_sumForKeyPath:(NSString *)keyPath {
NSDecimal resultDecimal = {0};
NSDecimalNumber *zero = [NSDecimalNumber zero];
if (zero) resultDecimal = [zero decimalValue];
// 这里使用NSDecimalNumber 保证精确度
NSDecimal eachDecimal = {0};
for (NSUInteger i=0; i<self.count; ++i) {
// 获取每个对象的keyPath(如@"price")对应值
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
eachDecimal = [eachValue decimalValue];
// 累加
NSDecimalAdd(&resultDecimal, &resultDecimal, &eachDecimal, NSRoundBankers);
}
}
return [NSDecimalNumber decimalNumberWithDecimal:resultDecimal];
}

求平均值

1
2
3
4
5
6
7
8
9
// 对 Array中每个对象的keyPath对应值 求平均值
// @avg.keyPath
- (NSNumber *)_avgForKeyPath:(NSString *)keyPath {
if (self.count) {
//总和 / 对象数
return [(NSDecimalNumber*)[self _sumForKeyPath:keyPath] decimalNumberByDividingBy:(NSDecimalNumber*)[NSDecimalNumber numberWithUnsignedInteger:self.count]];
}
return 0;
}

求数量

1
2
3
4
5
// 获取对象数目
// @count
- (NSNumber *)_countForKeyPath:(NSString *)keyPath {
return [NSNumber numberWithInteger:self.count];
}

求最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 对 Array中每个对象的keyPath对应值 求最大值
// @max.keyPath
- (id)_maxForKeyPath:(NSString *)keyPath {
id maxValue = nil;
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
if (!maxValue) {
maxValue = eachValue;
} else if ([maxValue compare:eachValue] == NSOrderedAscending){
maxValue = eachValue;
}
}
}
return maxValue;
}

求最小值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 对 Array中每个对象的keyPath对应值 求最小值
// @min.keyPath
- (id)_minForKeyPath:(NSString *)keyPath {
id minValue = nil;
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
if (!minValue) {
minValue = eachValue;
} else if ([minValue compare:eachValue] == NSOrderedDescending){
minValue = eachValue;
}
}
}
return minValue;
}

获取数组

1
2
3
4
5
6
7
8
9
10
11
12
// 返回 Array中每个对象的keyPath对应值 组成数组
// @unionOfObjects.keyPath
- (NSArray *)_unionOfObjectsForKeyPath:(NSString *)keyPath {
NSMutableArray *unionArray = [NSMutableArray arrayWithCapacity:self.count];
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
[unionArray addObject:eachValue];
}
}
return unionArray;
}

获取去重数组

1
2
3
4
5
6
// 返回 Array中每个对象的keyPath对应值 组成去重数组
// @distinctUnionOfObjects.keyPath
- (NSArray *)_distinctUnionOfObjectsForKeyPath:(NSString *)keyPath {
NSArray *unionArray = [self _unionOfObjectsForKeyPath:keyPath];
return [NSSet setWithArray:unionArray].allObjects;
}

获取成员数组

1
2
3
4
5
6
7
8
9
10
11
12
// 返回 Array中每个对象的keyPath对应数组的每个成员 组成数组 这里每个keyPath对应值是也是数组,获取的是每个数组展开后组成的总数组
// @unionOfArrays.keyPath
- (NSArray *)_unionOfArraysForKeyPath:(NSString *)keyPath {
NSMutableArray *unionArray = [NSMutableArray arrayWithCapacity:self.count];
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
[unionArray addObjectsFromArray:eachValue];
}
}
return unionArray;
}

获取去重的成员数组

1
2
3
4
5
6
// 返回 Array中每个对象的keyPath对应数组的每个成员 组成的去重复数组.
// @distinctUnionOfArrays.keyPath
- (NSArray *)_distinctUnionOfArraysForKeyPath:(NSString *)keyPath {
NSArray *unionArray = [self _unionOfArraysForKeyPath:keyPath];
return [NSSet setWithArray:unionArray].allObjects;
}

获取集合数组

1
2
3
4
5
6
7
8
9
10
11
12
// 返回 Array中每个对象的keyPath对应集合的每个成员 组成的数组. 这里每个keyPath对应值是是集合,获取的是每个集合展开后组成的总数组
// @unionOfSets.keyPath
- (NSArray *)_unionOfSetsForKeyPath:(NSString *)keyPath {
NSMutableArray *unionArray = [NSMutableArray arrayWithCapacity:self.count];
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
[unionArray addObjectsFromArray:[eachValue allObjects]];
}
}
return unionArray;
}

获取去重的集合数组

1
2
3
4
5
6
7
8
9
10
11
12
// 返回 Array中每个对象的keyPath对应集合的每个成员 组成的去重复数组.
// @distinctUnionOfSets.keyPath
- (NSArray *)_distinctUnionOfSetsForKeyPath:(NSString *)keyPath {
NSMutableSet *unionSet = [NSMutableSet setWithCapacity:self.count];
for (NSUInteger i=0; i<self.count; ++i) {
id eachValue = [self _valueForKeyPath:keyPath ofObjectAtIndex:i];
if (eachValue) {
[unionSet unionSet:eachValue];
}
}
return unionSet.allObjects;
}

NSSet的KVC

与NSArray的逻辑基本保持一致。

NSOrderedSet的KVC

与NSArray的逻辑基本保持一致。

NSDictionary的KVC

与NSArray相比,主要区别在于:

1
2
3
4
5
6
7
8
9
10
11
- (id)valueForKey:(NSString *)key {
NSString *operationKey = nil;
// key中包含'@字符', 且'@字符'在第0位, 如 @"@count"
if(key.length && [key characterAtIndex:0] == '@' && (operationKey = [key substringWithRange:NSMakeRange(1, key.length - 1)])) {
// 此时, operationKey为 @"count"
return [super valueForKey:operationKey];
} else {
// 没有'@字符', 走字典的objectForKey逻辑
return [self objectForKey:key];
}
}

valueForKey:取值逻辑多了对@字符的处理。

valueForKeyPath:与NSArray的逻辑一致。

五、其他分类的KVC

NSMutableDictionary的KVC

1
2
3
4
5
6
7
8
9
@implementation NSMutableDictionary (NSKeyValueCoding)
- (void)setValue:(id)value forKey:(NSString *)key {
if(value) {
[self setObject:value forKey:key];
} else {
[self removeObjectForKey:key];
}
}
@end

相比主类增加的特性是:在NSMutableDictionary中,如果设置的value为空,则自动将key对应的value移除。

NSUserDefaults的KVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation NSUserDefaults (NSKeyValueCoding)
- (id)valueForKey:(NSString *)key {
NSString *subKey = nil;
if(key.length && [key characterAtIndex:0] == '@' && (subKey = [key substringWithRange:NSMakeRange(1, key.length - 1)])) {
return [super valueForKey:subKey];
} else {
return [self objectForKey:key];
}
}

- (void)setValue:(id)value forKey:(NSString *)key {
if(value) {
[self setObject:value forKey:key];
} else {
[self removeObjectForKey:key];
}
}
@end

整合了NSDictionaryNSMutableDictionary的特色。

  1. 增加了对@字符的处理。
  2. 如果设置的value为空,则自动将key对应的value移除。

NSNull的KVC

1
2
3
4
5
@implementation NSNull (NSKeyValueCoding)
- (id)valueForKey:(NSString *)key {
return self;
}
@end

对于NSNull来说,无论怎么设值,取出来的值总是NSNull对象。

六、总结

纵观全流程,使用KVC与直接使用存取器相比,速度方面稍有逊色,揣测主要原因如下:

  1. 字符串处理。尤其是含有键路径的时候,使用到递归(函数调用栈)。(当然,含有@字符的集合运算符也算。)
  2. 方法查找。流程颇多,尽管有使用CFSet作为缓存。
  3. 装箱拆箱。KVC要求设值参数和取值参数均为对象,这就需要一般值类型和对象类型的相互转换。

这也是为啥现在字典转模型都不使用KVC了,参见《读YYModel》

KVC并没有那么高性能,那么就无用武之地了吗?非也。

1.访问私有成员变量
对于只给出存取方法的对象,可以使用KVC直接访问私有成员变量。不过可能会破坏封装性,毕竟人家没暴露私有成员变量说明不想让人访问。更多的其实是体现在对系统库上访问上,“一不留神”就用到私有API了,我是乖孩子,不敢这么用,万一被苹果发现整个手百App就要被打回了。。。不过非私有API倒也可以尝试下,FDFullscreenPopGesture就用到了私有成员变量,极其巧妙地解决了全屏侧滑的问题。

2.集合操作
在文章的第四部分【集合对象的KVC】,就已经描述过,求和、求平均值、去重巴拉巴拉,聊胜于无。

3.JSON解析
前些日子,图搜进行框架改版,下发接口需要完全重构。这可是个危险的工作,今年后端已经出现两次问题了,主要是字段的类型出现错误,造成端启动的Crash。端上做了大量的防护工作,防不胜防,而且代码越来越难看。于是我想到了使用KVC解析字段,重新整理现有逻辑,脱敏后大致是这样:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)handleResponse:(NSDictionary *)response {
if (![response isKindOfClass:NSDictionary.class]) {
return;
}
// 处理苹果业务
[self handleAppleConfigWithResponse:[response valueForKeyPath:@"dataset.config1.apple"]];
// 处理香蕉业务
[self handleBananaConfigWithResponse:[response valueForKeyPath:@"dataset.config2.banana"]];
// 处理橘子业务
[self handleOrangeConfigWithResponse:[response valueForKeyPath:@"dataset.config3.orange"]];
/// ...
}

使用keyPath对应到具体的处理逻辑,不同字段之间逻辑隔离,一个字段出错,并不影响其他字段;字段、方法、逻辑一一对应,后期增加或者删减很方便,新同学熟悉业务逻辑也清晰明了;response是字典,不会出现valueForUndefinedKey的异常。在每条处理逻辑中做类型保护工作,方便review,不会遗漏。


强烈建议阅读:

https://myzerone.com/posts/2016/10/20/KVC(Key-Value-Coding)/
KVC 和 KVO

源码来自:https://github.com/renjinkui2719/DIS_KVC_KVO 。感谢作者。

参考资料
KVC Collection Operators
iOS KVC
iOS开发之你真的了解了KVC吗?
KVC集合操作符
KVC原理小记
iOS 对象的 setter 方法性能测试