PIYO - Tech & Life -

ペア要素をキーにしたNSDictionary

Objective-C iOS Xcode

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

@interface Pair : NSObject <NSCopying>

@property (nonatomic, readonly) id first;
@property (nonatomic, readonly) id second;

+ (id) pairWithFirst:(id)f second:(id)s;
- (id) initWithFirst:(id)f second:(id)s;

@end 

Pair.m

#import "Pair.h"

@implementation Pair
@synthesize first, second;

+ (id) pairWithFirst:(id)f second:(id)s 
{
    return [[[self class] alloc] initWithFirst:f second:s];
}

- (id) initWithFirst:(id)f second:(id)s
{
    if (self = [super init]) {
        first = [f copy];
        second = [s copy];
    }
    return self;
}

- (id) copyWithZone:(NSZone *)zone 
{
    Pair* copy = [[[self class] alloc] initWithFirst:[self first] second:[self second]];
    return copy;
}

- (BOOL) isEqual:(id)other 
{
    if ([other isKindOfClass:[Pair class]] == NO) { return NO; }
    return ([[self first] isEqual:[(Pair*)other first]] && [[self second] isEqual:[(Pair*)other second]]);
}

- (NSUInteger) hash 
{
    return [[self first] hash] + [[self second] hash];
}

@end 

使い方

こんな風に使います。

NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
[dic setObject:@"A" forKey:[Pair pairWithFirst:@"hoge" second:@"huga"]];
[dic setObject:@"B" forKey:[Pair pairWithFirst:@"foo" second:@"bar"]];
[dic setObject:@"C" forKey:[Pair pairWithFirst:@"hoge" second:@"bar"]];
[dic setObject:@"D" forKey:[Pair pairWithFirst:@"foo" second:@"bar"]];
[dic setObject:@"E" forKey:[Pair pairWithFirst:[NSNumber numberWithInt:10] second:[NSNumber numberWithInt:20]]];
[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の和を返しており厳密には一意の値ではない可能性もあるので、ちゃんとやりたい場合は実装を考える必要があります。

RELATED

XcodeインスペクタのState Config

UIButtonってタップ中の状態に色とか背景とか画像とか変えられますよね。てっきりコードでしか出来ないと思っていたらそんなことありませんでした。恥ずかしい。 このState Configを変えてから設定

画像付きUIButtonのレイアウト調整

UIButtonに画像をつけるとデフォルトでは画像の右側にテキストが回りこむような形でレイアウトされますが、このレイアウトはカスタマイズ可能です。 画像とテキストはそれぞれ独立して動かすことができますの

ARCを使ったiOS開発でのプロパティ名

こんな謎のビルドエラーに遭遇しました。 property's synthesized getter follows Cocoa naming convention for returning 'owned' objects どうやらnewから始まるプロパティ名は使用できないようです。エラーメッセージがわかりにくいので調べないとわかりませんでした。