NSArrayとNSSet

オブジェクトを保持する時は、何となく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の言うことは、一応、嘘じゃなかった(特定のケースにおいては)。