NSDictionary介紹
NSDictionary(字典)是使用 hash表來實現key和value之間的映射和存儲的, hash函數設計的好壞影響著數據的查找訪問效率。數據在hash表中分布的越均勻,其訪問效率越高。而在Objective-C中,通常都是利用NSString 來作為鍵值,其內部使用的hash函數也是通過使用 NSString對象作為鍵值來保證數據的各個節點在hash表中均勻分布。
NSDictionary內部結構
參考cocotron的源代碼,NSDictionary是使用NSMapTable來實現。
NSMapTable同樣是一個key-value的容器,下面是NSMapTable的部分代碼:
typedef struct {
NSMapTable *table;
NSInteger i;
struct _NSMapNode *j;
} NSMapEnumerator;
上述結構體描述了遍歷一個NSMapTable時的一個指針對象,其中包含table對象自身的指針,計數值,和節點指針。
typedef struct {
NSUInteger (*hash)(NSMapTable *table,const void *);
BOOL (*isEqual)(NSMapTable *table,const void *,const void *);
void (*retain)(NSMapTable *table,const void *);
void (*release)(NSMapTable *table,void *);
NSString *(*describe)(NSMapTable *table,const void *);
const void *notAKeyMarker;
} NSMapTableKeyCallBacks;
上述結構體中存放的是幾個函數指針,用于計算key的hash值,判斷key是否相等,retain,release操作。
typedef struct {
void (*retain)(NSMapTable *table,const void *);
void (*release)(NSMapTable *table,void *);
NSString *(*describe)(NSMapTable *table, const void *);
} NSMapTableValueCallBacks;
上述存放的三個函數指針,定義在對nsmaptable插入一對key-value時,對value對象的操作。
struct NSMapTable {
NSMapTableKeyCallBacks *keyCallBacks;
NSMapTableValueCallBacks *valueCallBacks;
NSUInteger count;
NSUInteger nBuckets;
NSMapNode **buckets;
};
可以看出來NSMapTable是一個哈希+鏈表的數據結構,因此在NSMapTable中插入或者刪除一對對象時, 尋找的時間是O(1)+O(m),m最壞時可能為n。
- O(1):為對key進行hash得到bucket的位置
- O(m):遍歷該bucket后面沖突的value,通過鏈表連接起來。
因此NSDictionary中的Key-Value遍歷時是無序的,至如按照什么樣的順序,跟hash函數相關。NSMapTable使用NSObject的哈希函數。
-(NSUInteger)hash {
return (NSUInteger)self>>4;
}
上述是NSObject的哈希值的計算方式,簡單通過移位實現。右移4位,左邊補0。因為對象大多存于堆中,地址相差4位應該很正常。
NSDictionary的KVC實現
@implementation NSDictionary (NSKeyValueCoding)
-(id)valueForKey:(NSString*)key;
{
if([key hasPrefix:@"@"])
return [super valueForKey:[key substringFromIndex:1]];
return [self objectForKey:key];
}
-(void)setValue:(id)value forKey:(NSString*)key
{
[NSException raise:NSInvalidArgumentException format:@"%@ called on immutable dictionary %@", NSStringFromSelector(_cmd), self];
}
@end
@implementation NSMutableDictionary (NSKeyValueCoding)
-(void)setValue:(id)value forKey:(NSString*)key
{
if(value)
[self setObject:value forKey:key];
else
[self removeObjectForKey:key];
}
@end
setObject: ForKey:
是NSMutableDictionary特有的;setValue: ForKey:
是KVC的主要方法。
(1)
setValue: ForKey:
的value
是可以為nil
的(但是當value為nil的時候,會自動調用removeObject:forKey
方法);setObject: ForKey:
的value
則不可以為nil
。
(2)setValue: ForKey:
的key
必須是不為nil
的字符串類型;
setObject: ForKey:
的key
可以是不為nil的所有繼承NSCopying
的類型。
NSDictionary copy key對象
NSDictionary 提供了 key -> object 的映射。從本質上講,NSDictionary 中存儲的 object 位置是由 key 來索引的。
由于對象存儲在特定位置,NSDictionary 中要求 key 的值不能改變(否則 object 的位置會錯誤)。為了保證這一點,NSDictionary 會始終copy key 到自己私有空間。而且object對象在其內部是作為強引用(retain或strong);
這個 key 的copy行為也是 NSDictionary 如何工作的基礎,但這也有一個限制:你只能使用 OC 對象作為 NSDictionary 的 key,并且必須支持 NSCopying 協議。此外,key 應該是小且高效的,以至于復制的時候不會對 CPU 和內存造成負擔。
這意味著,NSDictionary 中真的只適合將值類型的對象作為 key(如簡短字符串和數字)。并不適合自己的模型類來做對象到對象的映射。