オブジェクトを保持する時は、何となくNSArray(NSMutableArray)に放り込んでしまいがち。だが、しかし。それは果たしてNSArrayでなくてはならないのか、と思った話。
NSArrayは名前の通りオブジェクトを配列として格納する。列があるということは、当然、順番がある。中のオブジェクトを取得するためのメソッドを見てもまた然り。
- (id)objectAtIndex:(NSUinteger)index;
- (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes;
対して、NSSetは配列ではなくオブジェクトを集合として格納する。
順番がないので、その手のAPIもなく、個々のオブジェクトを取得できるのはこれくらい。
- (id)anyObject;
ただし、何が返ってくるかは不定。ランダムかどうかも保証されない(とAppleは言ってる)。
テーブルに表示するデータを扱う時などは順番が必要だけれど、単にデータの確認(存在とか重複とか)をしたりする際には順番は不問のはず。つまり、そういう場合はNSSetを使った方が良いということになる。そっちの方がパフォーマンスも良いとAppleが(略。
じゃあ、試してみましょう。そうしましょう。
// array NSMutableArray *array = [NSMutableArray array]; // add CFAbsoluteTime arrayAddStart = CFAbsoluteTimeGetCurrent(); for (NSUInteger i = 0; i < 10000; i++) { [array addObject:@(i)]; } CFAbsoluteTime arrayAddEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"array:add=%f", arrayAddEnd - arrayAddStart); // fast enumeration CFAbsoluteTime arrayfeStart = CFAbsoluteTimeGetCurrent(); for (NSNumber* number in array) { } CFAbsoluteTime arrayfeEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"array:fast enumeration=%f", arrayfeEnd - arrayfeStart); // check whether contains or not CFAbsoluteTime arrayContainsStart = CFAbsoluteTimeGetCurrent(); for (NSUInteger i = 0; i < 10000; i++) { [array containsObject:@(i)]; } CFAbsoluteTime arrayContainsEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"array:contains=%f", arrayContainsEnd - arrayContainsStart); // index access CFAbsoluteTime arrayIndexAccStart = CFAbsoluteTimeGetCurrent(); for (NSUInteger i = 0; i < 10000; i++) { array[i]; } CFAbsoluteTime arrayIndexAccEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"array:index access=%f", arrayIndexAccEnd - arrayIndexAccStart); // for set NSMutableSet *set = [NSMutableSet set]; // add CFAbsoluteTime setAddStart = CFAbsoluteTimeGetCurrent(); for (NSUInteger i = 0; i < 10000; i++) { [set addObject:@(i)]; } CFAbsoluteTime setAddEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"set:add=%f", setAddEnd - setAddStart); // fast enumeration CFAbsoluteTime setFeStart = CFAbsoluteTimeGetCurrent(); for (NSNumber* number in set) { } CFAbsoluteTime setFeEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"set:fast enumeration=%f", setFeEnd - setFeStart); // check whether contains or not CFAbsoluteTime setContainsStart = CFAbsoluteTimeGetCurrent(); for (NSUInteger i = 0; i < 10000; i++) { [set containsObject:@(i)]; } CFAbsoluteTime setContainsEnd = CFAbsoluteTimeGetCurrent(); NSLog(@"set:contains=%f", setContainsEnd - setContainsStart);
こんな感じで、10000件をそれぞれに入れて試してみた結果(10回の平均 on Mac):
NSArray | NSSet | |
---|---|---|
追加 | 0.004542 | 0.0059005 |
Fast Enumeration | 0.000060 | 0.0001296 |
contains | 4.849716 | 0.0054603 |
Indexアクセス | 0.001285 | x |
・ 追加する時はNSArrayの方が遅いかと思いきやそうでもなく、
またFast EnumerationもNSArrayの方が速い。
・ しかし、containsのチェックはNSSetの方が信じられないくらいに高速。
1件ごとの比較時間を見てもNSSetの方がダンチ(とはいえ、
普通はこんなコードを書かないだろうし試行する数が少なければ
ここまでの差は付かないような気はする)。
結論: Appleの言うことは、一応、嘘じゃなかった(特定のケースにおいては)。