- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues;
这个方法是NSObject的一个KVC分类。
对入参keyedValues,采取每一个<key, value>都调用 [self setValue: forKey:]的高阶函数调用,来修改原有dictionary中的<k, v>。
空值影响
如果<key, value>中的value为空,即NSNull, 则会调用[self setValue: nil forKey:]; key为空,则会崩溃。keyedValues为空,则不会有任何作用。
元素 | 为空影响 |
---|---|
key | crash |
value | crash |
dic | 不起任何作用 |
例一
- 当keyedValues字典中value为NSNull时
@implementation Cat
- (instancetype)init
{
self = [super init];
if (self) {
[self setValue:@(11) forKey:@"_age"];
}
return self;
}
@end
- (void) test_setValuesForKeysWithDictionary
{
Animal *b = [Cat new];
NSLog(@"b.age: %@", [b valueForKey:@"_age"]);
[b setValuesForKeysWithDictionary:nil];
NSLog(@"b.age: %@", [b valueForKey:@"_age"]);
[b setValuesForKeysWithDictionary:@{@"_age" : @(12)}];
NSLog(@"b.age: %@", [b valueForKey:@"_age"]);
[b setValuesForKeysWithDictionary:@{@"_age" : [NSNull new]}];
NSLog(@"b.age: %@", [b valueForKey:@"_age"]);
[b setValuesForKeysWithDictionary:@{[NSNull new] : @(12)}];
NSLog(@"b.age: %@", [b valueForKey:@"_age"]);
// [b setValuesForKeysWithDictionary:@{@"_age1" : @(12)}];
// NSLog(@"b.age: %@", [b valueForKey:@"_age1"]);
}
2020-05-18 16:01:22.952292+0800 TestDemo[18825:7239205] b.age: 11
2020-05-18 16:01:22.952332+0800 TestDemo[18825:7239205] b.age: 11
2020-05-18 16:01:22.952343+0800 TestDemo[18825:7239205] b.age: 12
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 8.1
frame #0: 0x00000001cba16cc4 libobjc.A.dylib`objc_exception_throw
frame #1: 0x00000001cc7172f4 CoreFoundation`+[NSException raise:format:] + 120
frame #2: 0x00000001cd266750 Foundation`-[NSObject(NSKeyValueCoding) setNilValueForKey:] + 92
frame #3: 0x00000001cd265d48 Foundation`_NSSetUsingKeyValueSetter + 132
frame #4: 0x00000001cd1a937c Foundation`-[NSObject(NSKeyValueCoding) setValue:forKey:] + 268
* frame #5: 0x00000001cd1f5084 Foundation`-[NSObject(NSKeyValueCoding) setValuesForKeysWithDictionary:] + 240
frame #6: 0x000000010062679c TestDemo`-[YFKVX test_setValuesForKeysWithDictionary](self=0x0000000280d9c5d0, _cmd="test_setValuesForKeysWithDictionary") at YFKVX.m:61:5
frame #7: 0x0000000100624c70 TestDemo`-[MyViewController viewDidLoad](self=0x0000000100d1dd50, _cmd="viewDidLoad") at MyViewController.m:143:5
frame #8: 0x00000001f934ebb4 UIKitCore`-[UIViewController loadViewIfRequired] + 1028
frame #9: 0x00000001f934efc0 UIKitCore`-[UIViewController view] + 32
frame #10: 0x0000000100628244 TestDemo`-[AppDelegate application:didFinishLaunchingWithOptions:](self=0x0000000280f9d0e0, _cmd="application:didFinishLaunchingWithOptions:", application=0x0000000102300000, launchOptions=0x0000000000000000) at AppDelegate.m:23:8
当keyedValues字典中value为NSNull时,编译不会提示,运行时则crash _NSSetUsingKeyValueSetter -> [NSObject(NSKeyValueCoding) setNilValueForKey:]
-
当keyedValues字典中的key为NSNull时
image.png
当keyedValues字典中的key为NSNull时,则会编译时警告Object of type 'NSNull *' is not compatible with dictionary key type 'NSString *'
,运行时找不到方法-[NSNull lengthOfBytesUsingEncoding:]: unrecognized selector sent to instance 0x1fd187c00
。这个是该kvc方法 Dictinary的泛型所致NSDictionary<NSString *, id>
。而lengthOfBytesUsingEncoding
是个什么方法呢?
它是NSString的Extension一个扩展。
Result in O(n) time; the result is exact. Returns 0 on error (cannot convert to specified encoding, or overflow)
文档中说明它会返回一个精确的字节值,如果为 0则说明发生了错误,或者编码问题,或者溢出。
所以,当- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
发生崩溃时,还有一种可能是编码方式出现了问题,或者溢出。因为,内部实现[NSObject(NSKeyValueCodingPrivate) _createValueSetterWithContainerClassID:key:]
的key调用了lengthOfBytesUsingEncoding
校验.
- 设置不存在的值
设置不存在的值会怎么样呢?
![](https://img.haomeiwen.com/i1909331/45c429d0d783df15.png)
可见,由于KVC在设置值的时候,找不到对应的key而崩溃。具体逻辑在_NSSetUsingKeyValueSetter
。
例二
- (void) test_setValuesForKeysWithDictionary2
{
NSMutableDictionary *paras = [NSMutableDictionary dictionary];
[paras setValue:@(11) forKey:@"age"];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:nil];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:@{@"age" : @(12)}];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:@{@"age" : [NSNull new]}];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:@{@"_age" : @(12)}];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:@{[NSNull new] : @(12)}];
NSLog(@"paras: %@", paras);
[paras setValuesForKeysWithDictionary:@{@"_age1" : @(12)}];
NSLog(@"paras: %@", paras);
}
输出是什么呢?可以先自己猜一下。
答案:
2020-05-18 15:58:22.817891+0800 TestDemo[18822:7238508] paras: {
age = 11;
}
2020-05-18 15:58:22.817934+0800 TestDemo[18822:7238508] paras: {
age = 11;
}
2020-05-18 15:58:22.817956+0800 TestDemo[18822:7238508] paras: {
age = 12;
}
2020-05-18 15:58:22.817976+0800 TestDemo[18822:7238508] paras: {
}
2020-05-18 15:58:22.818000+0800 TestDemo[18822:7238508] paras: {
"_age" = 12;
}
2020-05-18 15:58:22.818027+0800 TestDemo[18822:7238508] paras: {
"_age" = 12;
"<null>" = 12;
}
2020-05-18 15:58:22.818054+0800 TestDemo[18822:7238508] paras: {
"_age" = 12;
"<null>" = 12;
"_age1" = 12;
}
可见上方表格中NSObject分类方法的规则已经被打破。
元素 | 为空影响 |
---|---|
key | nsnull被转化成@"<null>" |
value | 将原有dictionary中key对应的<key,value> entry删除 |
dic | 不起任何作用 |
只有整体为空时,沿用nsobject扩展中的规则。
![](https://img.haomeiwen.com/i1909331/18259cee7ebda8ee.png)
![](https://img.haomeiwen.com/i1909331/76252e8a12c28745.png)
![](https://img.haomeiwen.com/i1909331/80e0a0634137aedf.png)
bl + mov 相当于c的函数调用和值返回,所以我们可以清楚地知道,setValuesForKeysWithDictionary
方法在dictionary执行时调用了[NSDictionary dictionaryWithObjects:forKeys:count:]
并将结果返回给了调用方。
所以表现完全不一致了。
结论
用于NSObject时,和KVC的规则完全一致,但用于集合类型时,需要格外注意!我们只是分析了NSMutableDictionary,其他的集合类型想必也有对应于自身特性的不同表现。
自定义对象也可重写该方法。
网友评论