NSSet はあまり使わなくて、いざ使おうとするときに「あれ、どうだったっけ?」ってなるのでメモしておきます。
はじめに
NSSet はコレクションのひとつで、要素が重複しないようにオブジェクトを管理してくれます。
重複のチェックがどのように行われているのがが気になるので、NSMutableSet を使って動作検証してみます。
検証環境は、Xcode 6.1.1、iOS SDK 8.1 です。
検証コード
以下の自作クラスを使用します。とりあえず、こちらのドキュメントを参考に isEqual:
と hash
をオーバーライドしてログを出力するようにしています。
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
@property (nonatomic) NSString *itemName;
@property (nonatomic) NSInteger price;
@end
#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 へ追加するコードはこんな感じです。itemName
と price
が全く同じオブジェクトを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で、こちらも期待する動作ではありませんでした。