objective-c 自定义 NSDictionary 键类的注意事项
做 ios 开发,NSDictionary、NSMutableDictionary,NSMutableArray、NSArray 都是很常用的容器类
Array 就不多做讨论了,今天的文章主要讨论 NSDictionary 和 NSMutableDictionary~
以往我用 cocoa 的 Dictionary 的时候,都是选择用 NSString 来作为键对象的类型。
一直都没有出什么大的问题,用起来很顺手~
不巧昨天工作室中的另外一个成员说用 NSString 做键类型很业余,拼接啊、解析啊什么的,业余!!
其实我一直都觉得用字符串拼接解析这种模式还不错的,不过后来听到对方的举证,也被说服了。
对方的主要观念就用自定义的对象类型来作为字典的键类型,
我被说服的原因很简单,自定义的对象类型确实更接近面向对象的思考模式~
好,我承认我被说服了,不过因为我手头上也有 一些事情,所以并没有在这个问题上面做编码试验~
我认为cocoa 的字典应该像支持 NSString 那样,对用户自定义的键对象类型提供良好的支持。
但事实就是这么打击人,当然,最先被打击的不是我,是那个提出自定义键对象类型的伙计。
他遇到的第一个问题就是,从 NSObject 扩展出来的一个类,只包含两个int 型的属性
然后,用这个类作为字典的键的时候,竟然在 setObject: forKey:方法调用的时候,
报出 unrecognized selector "copyWithZone:" 的错误~
思忖良久外加google一番,终于发现原来是要在扩展类中将 copyWithZone 这个方法显式的重写一下~
(copyWithZone 这个方法在 NSObject 里面有所定义,报错就是因为该类型如果要作为字典的键就必须要实现这个方法)~
当然,问题如果这么轻易就易做图掉了,那我也就没必要写博文记载下来了~
字典的键最基本的要求就是不能重复。。
根据我做的编码测试,copyWithZone 是将 键对象参数克隆一份,放置在字典对象里面~
但事先要做一些检查,检查新加入的键和已存在的键们是否重复,如果重复的话,就覆盖所设置的值或者忽略这一次添加~
不过怎么样,就是字典不允许存在两个一模一样的键,否则检索值的时候会发生矛盾~
检查是否重复主要集中在 NSObject 基础类的两个方法上面:
-(NSUInteger) hash;
-(BOOL) isEqual:(id)object;
hash相当于对一团数据做 md5 摘要,得出他的数字签名,大意就是两团数据只要有一丁点儿的不一样
返回的那个无符号整数的值都不会相同(那是完美的哈希算法,现实生活中只要保证很大几率下面不同的数据返回不尽相同的返回值就行了)
据我推测,检查新添加的键和已存在的键是否重复包含两步:
第一步是对比hash,如果hash都不相同的话,判定为两个键不同,将第二步省略掉;
第二部是在 hash 相等的基础上才会执行的,hash相等后,再用 isEqual 方法来判定两个键是否相等~
(此相等不是说对象的内存地址相等,而是对象中所包含的数据是否相等)~
综上,如果要自定义字典键对象的类型,要重写下面的几个方法:
1。copyWithZone:这个是必须重写的,否则直接报找不到方法的错误
2。hash:这个你可以不重写,但是你尽可以试一下,蛋疼死你~
3。isEqual: 这个方法必须要重写一下,你不重写的话,默认的实现就是对比两个对象的内存地址
只有在两个对象是同一个对象的时候,才会返回 YES。
当然这个不是我们所需要的,我们需要的就是直接构造一个新的键对象,只要这个新构造的键对象中所包含的数据
与字典中的键相一致了,就取出字典中那个键对象所对应着的值对象~
下面上代码(建一个 mac command Line Project,将下面的几个类文件拽进去),
给出一个典型的能够直接拿来作为字典键对象的类型定义:
KeyObject.h
[cpp] //
// KeyObject.h
// DictionaryKeyObject
//
// Created by BruceYang on 12-7-29.
// Copyright (c) 2012年 EricGameStudio. All rights reserved.
//
#import <Foundation/Foundation.h>
@inte易做图ce KeyObject : NSObject {
int _x;
int _y;
}
@property int x;
@property int y;
+(id) kObjectWithX:(int)x y:(int)y;
-(id) initWithX:(int)x y:(int)y;
-(id) copyWithZone:(NSZone*)zone;
-(NSString*) description;
-(NSUInteger) hash;
-(BOOL) isEqual:(id)object;
@end
//
// KeyObject.h
// DictionaryKeyObject
//
// Created by BruceYang on 12-7-29.
// Copyright (c) 2012年 EricGameStudio. All rights reserved.
//
#import <Foundation/Foundation.h>
@inte易做图ce KeyObject : NSObject {
int _x;
int _y;
}
@property int x;
@property int y;
+(id) kObjectWithX:(int)x y:(int)y;
-(id) initWithX:(int)x y:(int)y;
-(id) copyWithZone:(NSZone*)zone;
-(NSString*) description;
-(NSUInteger) hash;
-(BOOL) isEqual:(id)object;
@end
KeyObject.m
[cpp]
//
// KeyObject.m
// DictionaryKeyObject
//
// Created by BruceYang on 12-7-29.
// Copyright (c) 2012年 EricGameStudio. All rights reserved.
//
#import "KeyObject.h"
@implementation KeyObject
@synthesize x = _x;
@synthesize y = _y;
+(id) kObjectWithX:(int)x y:(int)y {
return [[[self alloc] initWithX:x y:y] autorelease];
}
-(id) initWithX:(int)x y:(int)y {
if((self = [super init])){
_x = x;
_y = y;
}
return self;
}
/**
* 注意:
* 不能 autorelease!setObject:forKey: 的时候,key 对象的 retainCount 并不会加 1~
* autorelease 的话导致一脱离所在的域,键对象就失效,引发 EXEC_BAD_ACCESS 错误~
* 用 Instruments 做 allocation 测试,可以发现键对象一直是处于存活状态的,这才是正常的情况~
*
* copyWithZone 是在 [NSMutableDictionary setObject:@"test" forKey:KeyObject对象] 方法调用的时候调用的~
* 大意就是将作为字典键的对象深拷贝一份放在字典对象里面。
* 如果 KeyObject 作为字典的键对象来使用,使用完毕后需将键对象 release 掉,因为字典并不会使用这个对象,
* 而是会克隆出一个包含相同数据的对象来~
*
* 以上也是 setObject forKey 前后,为什么键对象的 retainCount 依然为 1 的原因~
* (注:setObject forKey 前后, 值对象的 retainCount 会加 1~)
*/
-(id) copyWithZone:(NSZone*)zone {
NSLog(@"KeyObject.copyWithZone() 方法被调用~");
// 正确的写法~
return [[KeyObject alloc] initWithX:_x y:_y];
// 错误的写法~
// return [KeyObject kObjectWithX:_x y:_y];
}
-(NSString*) description {
return [NSString stringWithFormat:@"_x=%d, _y=%d", _x, _y];
}
-(NSUInteger) hash {
// NSLog(@"KeyObject.hash() 方法被调用~");
return 0;
}
-(BOOL) isEqual:(id)object {
// NSLog(@"KeyObject.isEqual() 方法被调用~");
if(![object isKindOfClass:[KeyObject class]]) {
// NSLog(@"Object passed in isn't a KeyObject instance!");
return NO;
}
KeyObject* tmp = (KeyObject*)object;
补充:软件开发 , C++ ,