LoginSignup
4
4

More than 5 years have passed since last update.

NSSet に入れる自作クラスは isEqual: と hash をオーバーライドする

Last updated at Posted at 2014-12-09

NSSet はあまり使わなくて、いざ使おうとするときに「あれ、どうだったっけ?」ってなるのでメモしておきます。

はじめに

NSSet はコレクションのひとつで、要素が重複しないようにオブジェクトを管理してくれます。

重複のチェックがどのように行われているのがが気になるので、NSMutableSet を使って動作検証してみます。

検証環境は、Xcode 6.1.1、iOS SDK 8.1 です。

検証コード

以下の自作クラスを使用します。とりあえず、こちらのドキュメントを参考に isEqual:hash をオーバーライドしてログを出力するようにしています。

MyClass.h
#import <Foundation/Foundation.h>

@interface MyClass : NSObject

@property (nonatomic) NSString *itemName;
@property (nonatomic) NSInteger price;

@end
MyClass.m
#import "MyClass.h"

@implementation MyClass

- (BOOL)isEqual:(id)object {
    __typeof(self) other = object;
    NSLog(@"%s (%@, %@) (%@, %@)", __PRETTY_FUNCTION__, self.itemName, @(self.price), other.itemName, @(other.price));
    return [self.itemName isEqualToString:other.itemName] && self.price == other.price;
}

- (NSUInteger)hash {
    NSString *str = [NSString stringWithFormat:@"%@_%@", self.itemName, @(self.price)];
    NSLog(@"%s (%@, %@, %@)", __PRETTY_FUNCTION__, self.itemName, @(self.price), @(str.hash));
    return str.hash;
}

@end

自作クラスのオブジェクトを NSMutableSet へ追加するコードはこんな感じです。itemNameprice が全く同じオブジェクトを2個ずつ、合計10個追加しています。

NSMutableSet *set = [NSMutableSet new];
for (NSInteger i = 0; i < 10; i++) {
    MyClass *obj = [MyClass new];
    obj.itemName = @"Mac mini";
    obj.price = i % 5;
    NSLog(@"ADD: %@, %@", obj.itemName, @(obj.price));
    [set addObject:obj];
}
NSLog(@"count: %@", @(set.count));

検証結果

NSLog の出力は以下のようになりました。isEqual:hash の両方が使用されるようです。

2014-12-10 01:55:30.000 SetSample[1534:46412] ADD: Mac mini, 0
2014-12-10 01:55:30.002 SetSample[1534:46412] -[MyClass hash] (Mac mini, 0, 8380095419414328695)
2014-12-10 01:55:30.002 SetSample[1534:46412] -[MyClass hash] (Mac mini, 0, 8380095419414328695)
2014-12-10 01:55:30.003 SetSample[1534:46412] ADD: Mac mini, 1
2014-12-10 01:55:30.003 SetSample[1534:46412] -[MyClass hash] (Mac mini, 1, 8380095419414329720)
2014-12-10 01:55:30.003 SetSample[1534:46412] ADD: Mac mini, 2
2014-12-10 01:55:30.003 SetSample[1534:46412] -[MyClass hash] (Mac mini, 2, 8380095419414330745)
2014-12-10 01:55:30.004 SetSample[1534:46412] ADD: Mac mini, 3
2014-12-10 01:55:30.004 SetSample[1534:46412] -[MyClass hash] (Mac mini, 3, 8380095419414331770)
2014-12-10 01:55:30.004 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 0) (Mac mini, 3)
2014-12-10 01:55:30.004 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 2) (Mac mini, 3)
2014-12-10 01:55:30.005 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 1) (Mac mini, 3)
2014-12-10 01:55:30.005 SetSample[1534:46412] -[MyClass hash] (Mac mini, 2, 8380095419414330745)
2014-12-10 01:55:30.005 SetSample[1534:46412] -[MyClass hash] (Mac mini, 1, 8380095419414329720)
2014-12-10 01:55:30.005 SetSample[1534:46412] -[MyClass hash] (Mac mini, 0, 8380095419414328695)
2014-12-10 01:55:30.026 SetSample[1534:46412] -[MyClass hash] (Mac mini, 3, 8380095419414331770)
2014-12-10 01:55:30.027 SetSample[1534:46412] ADD: Mac mini, 4
2014-12-10 01:55:30.027 SetSample[1534:46412] -[MyClass hash] (Mac mini, 4, 8380095419414332795)
2014-12-10 01:55:30.027 SetSample[1534:46412] ADD: Mac mini, 0
2014-12-10 01:55:30.028 SetSample[1534:46412] -[MyClass hash] (Mac mini, 0, 8380095419414328695)
2014-12-10 01:55:30.028 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 0) (Mac mini, 0)
2014-12-10 01:55:30.028 SetSample[1534:46412] ADD: Mac mini, 1
2014-12-10 01:55:30.029 SetSample[1534:46412] -[MyClass hash] (Mac mini, 1, 8380095419414329720)
2014-12-10 01:55:30.029 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 1) (Mac mini, 1)
2014-12-10 01:55:30.029 SetSample[1534:46412] ADD: Mac mini, 2
2014-12-10 01:55:30.030 SetSample[1534:46412] -[MyClass hash] (Mac mini, 2, 8380095419414330745)
2014-12-10 01:55:30.030 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 2) (Mac mini, 2)
2014-12-10 01:55:30.031 SetSample[1534:46412] ADD: Mac mini, 3
2014-12-10 01:55:30.031 SetSample[1534:46412] -[MyClass hash] (Mac mini, 3, 8380095419414331770)
2014-12-10 01:55:30.031 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 3) (Mac mini, 3)
2014-12-10 01:55:30.032 SetSample[1534:46412] ADD: Mac mini, 4
2014-12-10 01:55:30.032 SetSample[1534:46412] -[MyClass hash] (Mac mini, 4, 8380095419414332795)
2014-12-10 01:55:30.032 SetSample[1534:46412] -[MyClass isEqual:] (Mac mini, 4) (Mac mini, 4)
2014-12-10 01:55:30.032 SetSample[1534:46412] count: 5

さいごに

2014/12/10 コメントを頂きましたので、記事を修正しました。

isEqual:hash の両方が使われるので、isEqual:YES を返す場合は、hash の値が一致するように辻褄を合わせておく必要があります。

NSLog の内容を見ると、まず一致性を素早く確認するために hash が比較され、hash が一致した場合はさらに isEqual: で比較されるようです。(hash は衝突があり得るので、一致しても同じオブジェクトではない可能性があります)

ちなみに、MyClass.m の hash をオーバーライドしない状態で実行すると、ログの最後の count の値が実行するたびに変わってしまって、期待する動作をしませんでした。

isEqual: をオーバーライドしない状態で実行すると count の値は常に10で、こちらも期待する動作ではありませんでした。

4
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4