一:字典内部原理
二:key的copy问题
三:kvc与setobject forkey问题 / setValue forkey 和 setObject forkey问题
NSDictionary使用原理
1.NSDictionary(字典)是使用 hash表来实现key和value之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。
- (void)setObject:(id)anObject forKey:(id
2.Objective-C 中的字典 NSDictionary 底层其实是一个哈希表,实际上绝大多数语言中字典都通过哈希表实现,
哈希的原理 (数据结构之哈希表)
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
哈希概念:
哈希表的本质是一个数组,数组中每一个元素称为一个箱子(bin),箱子中存放的是键值对。
哈希表的存储过程:
1. 根据 key 计算出它的哈希值 h。
2. 假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中(哈希函数的构造方法:这里采用除留余数法)。
3. 如果该箱子中已经有了键值对,就使用开放寻址法或者拉链法解决冲突(hash函数冲突处理方法)。
在使用拉链法解决哈希冲突时,每个箱子其实是一个链表,属于同一个箱子的所有键值对都会排列在链表中。
哈希表还有一个重要的属性: 负载因子(load factor),它用来衡量哈希表的 空/满 程度,一定程度上也可以体现查询的效率,计算公式为:
负载因子 = 总键值对数 / 箱子个数
负载因子越大,意味着哈希表越满,越容易导致冲突,性能也就越低。因此,一般来说,当负载因子大于某个常数(可能是 1,或者 0.75 等)时,哈希表将自动扩容。
重哈希概念:
哈希表在自动扩容时,一般会创建两倍于原来个数的箱子,因此即使 key 的哈希值不变,对箱子个数取余的结果也会发生改变,因此所有键值对的存放位置都有可能发生改变,这个过程也称为重哈希(rehash)。
哈希表的扩容并不总是能够有效解决负载因子过大的问题。假设所有 key 的哈希值都一样,那么即使扩容以后他们的位置也不会变化。虽然负载因子会降低,但实际存储在每个箱子中的链表长度并不发生改变,因此也就不能提高哈希表的查询性能。
总结,细心的读者可能会发现哈希表的两个问题:
1. 如果哈希表中本来箱子就比较多,扩容时需要重新哈希并移动数据,性能影响较大。
2. 如果哈希函数设计不合理,哈希表在极端情况下会变成线性表,性能极低。
其实,NSDictionary使用NSMapTable实现。NSMapTable同样是一个key-value的容器,下面是NSMapTable的部分代码:
@interface NSMapTable : NSObject {
NSMapTableKeyCallBacks *keyCallBacks;
NSMapTableValueCallBacks *valueCallBacks;
NSUInteger count;
NSUInteger nBuckets;
struct _NSMapNode **buckets;
}
可以看出来NSMapTable是一个哈希+链表的数据结构,因此在NSMapTable中插入或者删除一对对象时
寻找的时间是O(1)+O(m),m最坏时可能为n。
O(1):为对key进行hash得到bucket的位置
O(m):遍历该bucket后面冲突的value,通过链表连接起来。(这个详细的看下面NSDctionnary的构造)
NSDictionary中的key Value遍历时是无序的,至如按照什么样的顺序,跟hash函数相关。NSMapTable使用NSObject的哈希函数。
-(NSUInteger)hash {
return (NSUInteger)self>>4;
}
上述是NSObject的哈希值的计算方式,简单通过移位实现。右移4位,左边补0.
因为对象大多存于堆中,地址相差4位应该很正常。
好,下面讲解详细的字典内部取值和赋值的过程
Dictionary的构造
(这个里用了拉链法解决hash函数冲突)
下面的代码我看看Dictionary在构造时都做了什么:
C#
private void Initialize(int capacity)
{
int prime = HashHelpers.GetPrime(capacity);
this.buckets = new int[prime];
for (int i = 0; i < this.buckets.Length; i++)
{
this.buckets[i] = -1;
}
this.entries = new Entry[prime];
this.freeList = -1;
}
我们看到,Dictionary在构造的时候做了以下几件事:
其中this.buckets主要用来进行Hash碰撞,this.entries用来存储字典的内容,并且标识下一个元素的位置。
我们以Dictionary
首先,我们构造一个:
Dictionary
初始化后:
(查看大图)
添加元素时,集合内部Bucket和entries的变化
Test.Add(4,”4″)后:
根据Hash算法: 4.GetHashCode()%7= 4,因此碰撞到buckets中下标为4的槽上,此时由于Count为0,因此元素放在Entries中第0个元素上,添加后Count变为1
(查看大图)
Test.Add(11,”11″)
根据Hash算法 11.GetHashCode()%7=4,因此再次碰撞到Buckets中下标为4的槽上,由于此槽上的值已经不为-1,此时Count=1,因此把这个新加的元素放到entries中下标为1的数组中,并且让Buckets槽指向下标为1的entries中,下标为1的entry之下下标为0的entries。
(查看大图)
Test.Add(18,”18″)
我们添加18,让HashCode再次碰撞到Buckets中下标为4的槽上,这个时候新元素添加到count+1的位置,并且Bucket槽指向新元素,新元素的Next指向Entries中下标为1的元素。此时你会发现所有hashcode相同的元素都形成了一个链表,如果元素碰撞次数越多,链表越长。所花费的时间也相对较多。
(查看大图)
Test.Add(19,”19″)
再次添加元素19,此时Hash碰撞到另外一个槽上,但是元素仍然添加到count+1的位置。
(查看大图)
删除元素时集合内部的变化
Test.Remove(4)
我们删除元素时,通过一次碰撞,并且沿着链表寻找3次,找到key为4的元素所在的位置,删除当前元素。并且把FreeList的位置指向当前删除元素的位置,FreeCount置为1
(查看大图)
Test.Remove(18)
删除Key为18的元素,仍然通过一次碰撞,并且沿着链表寻找2次,找到当前元素,删除当前元素,并且让FreeList指向当前元素,当前元素的Next指向上一个FreeList元素。
此时你会发现FreeList指向了一个链表,链表里面不包含任何元素,FreeCount表示不包含元素的链表的长度。
(查看大图)
Test.Add(20,”20″)
再添加一个元素,此时由于FreeList链表不为空,因此字典会优先添加到FreeList链表所指向的位置,添加后FreeCount减1,FreeList链表长度变为1
(查看大图)
总结:
通过以上试验,我们可以发现Dictionary在添加,删除元素按照如下方法进行:
原文:Dictionary的内部实现
看下面的代码
NSMutableString *mustr = [NSMutableString stringWithString:@"zjy"];
NSMutableDictionary *mudic = [NSMutableDictionary dictionary];
mudic[mustr] = @"name";
[mustr appendString:@"xxx"];
NSLog(@"%@",mudic[@"mustr"]);
答案是
2018-08-27 11:32:26.091138+0800 lalal[1724:183662] (null)
那是为什么呢?
看打印结果
这里说明是值拷贝,不是浅拷贝,为啥呢,NSDictionary中的key是唯一的,key可以是遵循NSCopying
协议和重载- (NSUInteger)hash;
、- (BOOL)isEqual:(id)object;
方法的任何对象。也就是说在NSDictionary内部,会对 aKey 对象 copy 一份新的。而 anObject 对象在其内部是作为强引用(retain或strong)。
hash
方法是用来计算该对象的 hash 值,最终的 hash 值决定了该对象在 hash 表中存储的位置。所以同样,如果想重写该方法,我们尽量设计一个能让数据分布均匀的 hash 函数。isEqual :
方法是为了通过 hash 值来找到 对象 在hash �表中的位置。在调用setObject: forKey:
后,内部会去调用 � key 对象的 hash
方法确定 object 在hash表内的入口位置,然后会调用 isEqual :
来确定该值是否已经存在于 NSDictionary中。
- (void)setObject:(id)anObject forKey:(id )aKey;
所以接着向下
可以看到原来的mustr值已经改变了,所以再用mustr取值,是取不到了。所以会报null。
看下面这个问题
NSMutableString *mustr = [NSMutableString stringWithString:@"zjy"];
NSMutableDictionary *mudic = [NSMutableDictionary dictionary];
mudic[@"name"] = mustr;
[mustr appendString:@"xxx"];
NSLog(@"%@",mudic[@"name"]);
想想一下,答案是什么?
2018-08-27 17:54:11.560047+0800 lalal[10054:720413] zjyxxx
原因是什么呢?
为啥呢?因为字典没有对value进行nscoding协议啊。再接着看这个对mutabledic用copy得到结果是什么呢?
是__NSFrozenDictionaryM这个,不是mutableDic。
NSMutableString *mustr = [NSMutableString stringWithString:@"zjy"];
NSDictionary *mudic = [NSDictionary dictionaryWithObjectsAndKeys:mustr,@"23", nil];
NSMutableDictionary *mudic3 = [mudic mutableCopy];
[mudic3 setObject:@"34" forKey:@"23"];
NSMutableDictionary *mudic4 = [mudic3 copy];
(lldb) p mudic
(__NSDictionaryI *) $0 = 0x0000000100502d50 2 key/value pairs
(lldb) p mudic4
(__NSFrozenDictionaryM *) $1 = 0x0000000100703110
(lldb)
如果对mudic4进行添加keyvalue值,会报错。
2018-08-28 14:32:09.021331+0800 lalal[6840:399826] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSFrozenDictionaryM setObject:forKey:]: unrecognized selector sent to instance 0x1006121a0'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2bb6032b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fff52cd2c76 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2bbf8e04 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x00007fff2bad6870 ___forwarding___ + 1456
4 CoreFoundation 0x00007fff2bad6238 _CF_forwarding_prep_0 + 120
5 lalal 0x00000001000018cf main + 511
6 libdyld.dylib 0x00007fff538ec015 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
接着又查找了NSMutablestring和NSMutableArrary,发现copy之后,都是不可变对象,是NSString和NSArray。
NSString *arrr = [NSString stringWithFormat:@"111"];
NSMutableString *arr = [NSMutableString stringWithFormat:@"244"];
NSMutableString *arr2 = [arr copy];
arr2 NSTaggedPointerString * @"244" 0x0000000034343235
NSString NSString
所以结论如下
不可变对象在进行copy时,系统内部判断也是为了省内存,自动不会产生新的对象,是浅拷贝,
mutable类型在进行copy时,都产生了新对象,但是新对象缺都是不可变类型的,不能添加内容。
不可变和mutable类型在mutableCopy的时候,都会产生新对象,都会深拷贝,都会产生可变对象。但是注意,因为字典的key值遵守了NScoding协议,是会深拷贝的,但是里面的元素value,却都没有进行深拷贝,如果想深拷贝,需要重新写。
这个提示一点,在进行用mutable类型写属性的时候,不要用copy,要用strong。因为有误导性,出了问题,不容易找到。
现在讲NSDictionary的copy和strong问题
先看属性NSDictionary。
#import
@interface Person : NSObject
@property (nonatomic, strong) NSMutableDictionary *mudicstrong;
@property (nonatomic, copy) NSMutableDictionary *mudiccopy;
@property (nonatomic, strong) NSDictionary *dicstrong;
@property (nonatomic, copy) NSDictionary *diccopy;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *arr1 = [[NSMutableArray alloc] initWithObjects:@"aa",@"bb",@"cc", nil];
NSMutableDictionary *dict0 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
NSDictionary *dict00 = [NSDictionary dictionaryWithDictionary:dict0];
Person *person = [[Person alloc] init];
person.dicstrong = dict00;
person.diccopy = dict00;
}
return 0;
}
(lldb) p dict00
(__NSFrozenDictionaryM *) $0 = 0x0000000100404520
(lldb) p person.dicstrong
(__NSFrozenDictionaryM *) $1 = 0x0000000100404520
(lldb) p person.diccopy
(__NSFrozenDictionaryM *) $2 = 0x0000000100404520
(lldb)
可以看出来NSDictionary 不论用copy还是strong,对它赋值不可变类型的字典,都不会发生变化。
好,现在把赋值的NSDictionary换成NSMutableDictionary。
NSMutableDictionary *dict0 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
Person *person = [[Person alloc] init];
person.dicstrong = dict0;
person.diccopy = dict0;
(lldb) p dict0
(__NSDictionaryM *) $6 = 0x0000000100404500 1 key/value pair
(lldb) p person.dicstrong
(__NSDictionaryM *) $7 = 0x0000000100404500 1 key/value pair
(lldb) p person.diccopy
(__NSFrozenDictionaryM *) $8 = 0x0000000100504f60
(lldb)
可以看到,对NSDictionary 赋值NSMutableDictionary,如果是strong修饰,则不变,依旧是可变字典,容易被篡改。如果是copy修饰,则重新赋值一份,深拷贝。所以,若想要不改变值,那不论管对方赋值的是可变的还是不可变的,均可用copy来修饰,以免被对方修改。
接下来看属性NSMutableDictionary。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *arr1 = [[NSMutableArray alloc] initWithObjects:@"aa",@"bb",@"cc", nil];
NSMutableDictionary *dict0 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
NSDictionary *dict00 = [NSDictionary dictionaryWithDictionary:dict0];
Person *person = [[Person alloc] init];
person.mudicstrong = dict00;
person.mudiccopy = dict00;
}
return 0;
}
(lldb) p dict00
(__NSFrozenDictionaryM *) $0 = 0x0000000100506080
(lldb) p person.mudicstrong
(__NSFrozenDictionaryM *) $1 = 0x0000000100506080
(lldb) p person.mudiccopy
(__NSFrozenDictionaryM *) $2 = 0x0000000100506080
(lldb)
可以看到对于属性NSMutabledictionary,赋值的是不可变类型,则不论是copy还是strong,都是不可变类型,且指针未发生变化。
接着看赋值可变类型的字典
NSMutableDictionary *dict0 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
Person *person = [[Person alloc] init];
person.mudicstrong = dict0;
person.mudiccopy = dict0;
(lldb) p dict0
(__NSDictionaryM *) $3 = 0x0000000100500300 1 key/value pair
(lldb) p person.mudicstrong
(__NSDictionaryM *) $4 = 0x0000000100500300 1 key/value pair
(lldb) p person.mudiccopy
(__NSFrozenDictionaryM *) $5 = 0x0000000100789110
(lldb)
可以知道,属性NSMutableDictionary,赋值不可变字典。strong修饰的,结果是可变类型,并且地址未变。但是用copy修饰的,却变成了不可变字典,地址也发生了变化。
结论:
不论属性是可变的还是不可变的, 用不可变的赋值,则产生结果都是不可变的。地址都是一样的(strong和copy和原值)因为对不可变的内部会copy或者retain,这样的是不会产生新值。
属性不论是不可变的还是可变的,用可变的赋值,则产生的结果,strong修饰的跟原值地址一样,copy修饰的会产生新值,为不可变对象,内部对可变的strong的话依旧是retain,对copy的话,会产生不可变的对象。这个也是最上面说的那些。
NSMutableArray *arr1 = [[NSMutableArray alloc] initWithObjects:@"aa",@"bb",@"cc", nil];
NSMutableDictionary *dict0 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
NSDictionary *dict00 = [NSDictionary dictionaryWithDictionary:dict0];
NSDictionary *dict00new = [NSDictionary dictionaryWithDictionary:dict00];
NSMutableDictionary *mudic4 = [NSMutableDictionary dictionaryWithDictionary:dict00];
NSMutableDictionary *mudic5 = [NSMutableDictionary dictionaryWithDictionary:mudic4];
(lldb) p dict00
(__NSFrozenDictionaryM *) $0 = 0x0000000100600b40
(lldb) p dict00new
(__NSFrozenDictionaryM *) $1 = 0x0000000100600b40
(lldb) p mudic4
(__NSDictionaryM *) $2 = 0x0000000102054d60 1 key/value pair
(lldb) p mudic5
(__NSDictionaryM *) $3 = 0x0000000102055f60 1 key/value pair
(lldb)
结论:只有在不可变被赋值不可变时候copy时,才会不发生指针改变,在mutable被赋值可变的不可变的,在不可变的被赋值可变的,才会有新地址。才会copy。
赋值的过程是kvc的赋值过程,这个后续接着讲
下面讲如何对字典进行深拷贝
一、新建Objective-C category文件,我这Category填MutableDeepCopy,Category on填NSDictionary,所以生成的文件是NSDictionary+MutableDeepCopy.h和NSDictionary+MutableDeepCopy.m,生成的文件名很容易理解。
二、两文件源代码:
NSDictionary+MutableDeepCopy.h
#import
@interface NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *)mutableDeepCopy;
//增加mutableDeepCopy方法
@end
#import "NSDictionary+MutableDeepCopy.h"
@implementation NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
//新建一个NSMutableDictionary对象,大小为原NSDictionary对象的大小
NSArray *keys = [self allKeys];
for(id key in keys)
{//循环读取复制每一个元素
id value = [self objectForKey:key];
id copyValue;
if ([value respondsToSelector:@selector(mutableDeepCopy)]) {
//如果key对应的元素可以响应mutableDeepCopy方法(还是NSDictionary),调用mutableDeepCopy方法复制
copyValue = [value mutableDeepCopy];
} else if ([value respondsToSelector:@selector(mutableCopy)])
{
copyValue = [value mutableCopy];
}
if (copyValue == nil)
copyValue = [value copy];
[dict setObject:copyValue forKey:key];
}
return dict;
}
@end
测试:
#import
#import "NSDictionary+MutableDeepCopy.h"
//导入头文件
int main (int argc, const char * argv[])
{
@autoreleasepool {
NSMutableArray *arr1 = [[NSMutableArray alloc] initWithObjects:@"aa",@"bb",@"cc", nil];
NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:arr1,@"arr1", nil];
NSLog(@"%p - %@",dict1,dict1);
NSMutableDictionary *dict2 = [dict1 mutableCopy];
//浅复制
NSMutableDictionary *dict3 = [dict1 mutableDeepCopy];
//深复制
[arr1 addObject:@"dd"];
NSLog(@"%p -%@",dict2,dict2);
NSLog(@"%p -%@",dict3,dict3);
NSLog(@"Hello, World!");
}
return 0;
}
2018-08-28 14:45:43.390614+0800 lalal[7014:430759] 0x102801de0 - {
arr1 = (
aa,
bb,
cc
);
}
2018-08-28 14:45:43.390896+0800 lalal[7014:430759] 0x100730280 -{
arr1 = (
aa,
bb,
cc,
dd
);
}
2018-08-28 14:45:43.390934+0800 lalal[7014:430759] 0x100733520 -{
arr1 = (
aa,
bb,
cc
);
}
iOS/Objective-C开发 字典NSDictionary的深复制(使用category)
(OC中的字典实际上为一个数组 , 数组中的每个元素同样为一个链表实现的数组 ,也就是数组中套数组-文章)
看这个代码
@interface NSMutableDictionary : NSDictionary
- (void)setObject:(ObjectType)anObject forKey:(KeyType )aKey;
@end
再看这个
@interface NSMutableDictionary(NSKeyValueCoding)
/* Send -setObject:forKey: to the receiver, unless the value is nil, in which case send -removeObjectForKey:.
*/
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
@end
这两个可能会引起一些混淆,还有使用手法上也有,setvalueForkey是kvc用法,但是不同于直接自NSObject的kvc,这个看注释,也可知道,内部直接调取的是上面setobject这个方法来实现赋值。所以即可知道,为啥setvalue 的key没有遵守NSCoying协议,但是实际key也copy了一份新地址。
@interface NSMutableDictionary : NSDictionary
@end
@interface NSDictionary<__covariant KeyType, __covariant ObjectType> : NSObject
@end
@interface NSObject(NSKeyValueCoding)
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
@end
看上面的继承及分类关系,可以知道,字典的kvc并不是调用NSObject的kvc。好,接着向下看。讲setObject和setValue的区别
setObject:forKey:
将给定的键值对添加到字典中
- (void)setObject:(ObjectType)anObject forKey:(id)aKey
参数讲解:
anObject:
aKey
的值,对该对象的强引用由字典维护重点:如果
anObject
为nil的话,会抛出NSInvalidArgumentException
异常,如果你想在字典中表示一个nil 值,可以使用NSNull
比如:
[dict setObject:[NSNull null] forKey:@"null"];打印:null = "
";
aKey:
value
的key
,aKey
将被复制(使用copyWithZone:
方法;key必须遵守NSCopying
协议),如果字典中存在了该key
,将替换anObject
重点:如果key为nil的话,会抛出
NSInvalidArgumentException
异常
也就是使用setObject:forKey:
方法会对value
强引用,会使value
的引用计数加一。
NSMutableDictionary *dict = @{}.mutableCopy;
NSString *key = @"test";
NSMutableArray *arr = [[NSMutableArray alloc] init];
printf("after init retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(arr)));
[dict setObject:arr forKey:key];
printf("after setObject:forKey: retain count = %ld\n ",CFGetRetainCount((__bridge CFTypeRef)(arr)));
打印信息:
after init retain count = 1
after setObject:forKey: retain count = 2
字典的KVC-setvalue forkey
使用KVC的方式也可以为一个可变字典添加一个键值对,
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
value
/* Send -setObject:forKey: to the receiver, unless the value is nil, in which case send -removeObjectForKey:.
*/
扩展NSMutableDictionary的一个类别,上面注释说的很清楚,发送setObject:forKey 给接收者,也就是调用setObject:forKey方法,除非value为nil的时候,调用方法removeObject:forKey
key:
需要注意的是,当使用kvc的时候,
key
必须是字符串
kvc在引用计数环境下,直接访问实例变量的话,value
会被retain
,
NSMutableDictionary *dict = @{}.mutableCopy;
NSMutableArray *arr = [[NSMutableArray alloc] init];
printf("after init retain count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(arr)));
[dict setObject:arr forKey:@"test"];
printf("after setObject:forKey: retain count = %ld\n ",CFGetRetainCount((__bridge CFTypeRef)(arr)));
// kvc 方式添加键值对
[dict setValue:arr forKey:@"test2"];
printf("after kvc retain count = %ld\n ",CFGetRetainCount((__bridge CFTypeRef)(arr)));
打印:
after init retain count = 1
after setObject:forKey: retain count = 2
after kvc retain count = 3
总结:
相同点:
- 两个方法的
key
都不能为nil,否则抛出NSInvalidArgumentException
- 都会对
value
强引用
不同点
setObjec:forKey:
的key
必须遵守NSCopying
协议,KVC的key
必须为字符串setObjec:forKey:
的value
不能为空,否则会抛出NSInvalidArgumentException
异常; KVC的value
会nil时,会调用字典的removeObjectForKey:
方法,否则,调用字典的setObject:forKey:
方法添加键值对
相应的,我们从字典中取值的时候,可以使用字典的objectForKey :
方法,也可以使用valueForKey:
方法。
这两种方法都比较简单,一般情况下,字典的valueForKey:
方法也是调用objectForKey :
来取值的,但这存在了一个前提:key
不能以字符"@"
开头
当key
不是以"@"
开始时,调用字典的objectForKey:
方法。如果以"@"
开始的话,则去除掉"@"
字符,并用剩余的字符调用[super valueForKey:]
方法。当父类也没有找到该key
时,会调用valueForUndefinedKey:
方法,而valueForUndefinedKey:
默认是抛出一个异常的。(只会去除开头的第一个"@"
,即如果key
是以多个"@"
字符开始的话,只会去除第一个开始的"@"
,剩余的"@"
字符会被保留)
NSMutableDictionary *dict = @{}.mutableCopy;
NSMutableArray *arr = [[NSMutableArray alloc] init];
NSString *key1 = @"@@@test";
[dict setObject:arr forKey:key1];
//取值
id test2 = [dict valueForKey:key1];
控制台打印:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSDictionaryM 0x600000222a80> valueForUndefinedKey:]: this class is not key value coding-compliant for the key @@test.'
可以看到,我们本来是通过"@@@test"
字符串作为key
去取值的,但是在查找过程中去除了第一个"@"
字符,用剩下的字符串"@@test"
去作为key
去查找的(不是把所有的"@"
字符去除)。
另外,与赋值时不同,取值时key
为nil
时并不会抛出异常。
字典可以使用removeObjectForKey :
方法删除某个键值对
- (void)removeObjectForKey:(KeyType)aKey;
key为nil的话, 会抛出
NSInvalidArgumentException
异常
当字典中不存在该key的时候,则什么都没做。
removeAllObjects
清空字典里的数据,其实是向字典中每个key以及对应的value
发送release
消息
这部分原文字:典的KVC与setObject:forKey:的区别