NSDictionaryで複数要素をキーにしようというお話です。

普通の連想配列はキーとバリューで1つの組み合わせになりますが、片方が◯でもう片方が☆のときはこの値みたいなケースの場合は最初から用意されているデータ型では間に合わないことが多いです。

C++ではstd::pairをキーにしたstd::mapを使えばよいです。Objective-Cではどうやるかわからなかったので調べた結果をまとめます。

ちなみに、複数要素をキーにするようなデータ構造に入れたくなるデータはこんなような感じです。

|Key1|Key2|Value| |::|::|::| |hoge|huga|value1| |hoge|piyo|value2| |hoge|foo|value3| |huga|piyo|value4| |huga|foo|value5| |huga|hoge|value6|

なにはともあれPairクラス

NSObjectを継承したPairクラスを作って必要なメソッドをオーバーライドして実装しておくことで、NSdictionaryのキーとして使えるようになります。このPairクラスができてしまえば今回やりたいことはできるようになったも同然です。

Pair.h

1@interface Pair : NSObject <NSCopying>
2
3@property (nonatomic, readonly) id first;
4@property (nonatomic, readonly) id second;
5
6+ (id) pairWithFirst:(id)f second:(id)s;
7- (id) initWithFirst:(id)f second:(id)s;
8
9@end 

Pair.m

 1#import "Pair.h"
 2
 3@implementation Pair
 4@synthesize first, second;
 5
 6+ (id) pairWithFirst:(id)f second:(id)s 
 7{
 8    return [[[self class] alloc] initWithFirst:f second:s];
 9}
10
11- (id) initWithFirst:(id)f second:(id)s
12{
13    if (self = [super init]) {
14        first = [f copy];
15        second = [s copy];
16    }
17    return self;
18}
19
20- (id) copyWithZone:(NSZone *)zone 
21{
22    Pair* copy = [[[self class] alloc] initWithFirst:[self first] second:[self second]];
23    return copy;
24}
25
26- (BOOL) isEqual:(id)other 
27{
28    if ([other isKindOfClass:[Pair class]] == NO) { return NO; }
29    return ([[self first] isEqual:[(Pair*)other first]] && [[self second] isEqual:[(Pair*)other second]]);
30}
31
32- (NSUInteger) hash 
33{
34    return [[self first] hash] + [[self second] hash];
35}
36
37@end 

使い方

こんな風に使います。

1NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
2[dic setObject:@"A" forKey:[Pair pairWithFirst:@"hoge" second:@"huga"]];
3[dic setObject:@"B" forKey:[Pair pairWithFirst:@"foo" second:@"bar"]];
4[dic setObject:@"C" forKey:[Pair pairWithFirst:@"hoge" second:@"bar"]];
5[dic setObject:@"D" forKey:[Pair pairWithFirst:@"foo" second:@"bar"]];
6[dic setObject:@"E" forKey:[Pair pairWithFirst:[NSNumber numberWithInt:10] second:[NSNumber numberWithInt:20]]];
7[dic setObject:@"F" forKey:[Pair pairWithFirst:[NSNumber numberWithInt:10] second:[NSNumber numberWithInt:20]]];

同じキーに値を上書きしていることもあって、上のdicには4つ要素が入っていることになります。

かいせつ〜

基本は2つのObjective-Cクラスを受け取って保持しておくクラスです。

NSDictionaryのキーとして使用したいクラスには次のメソッドを正しく実装しておく必要があります。

  • copyWithZone
  • isEqual
  • hash

copyWithZone

NSCopyingプロトコルのcopyWithZoneを実装しておかないとNSDictionaryのキーとしては使えません。キーとして格納する際にコピーが走るから(だと思います)です。

isEqual

連想配列のキーとして使用するには、キー同士が同じであうるかどうかの比較を行うことができなければいけません。そのために必要なメソッドです。

hash

インスタンス毎に一意(異なる)の値を返すように実装しておき、キーが同じであるかの判定に用いられます。先ほど紹介した実装ではペアとなっている要素のhashの和を返しており厳密には一意の値ではない可能性もあるので、ちゃんとやりたい場合は実装を考える必要があります。