プログラムのロジックでは、同じ処理を繰り返し実行させる、いわゆる「ループ処理」が頻繁に登場します。特に、今回紹介したような値の集合には、ループ処理がつきものです。
Objective-Cでは、C言語のいわゆる通常のforループのほかに、よりオブジェクティブに集合のループ処理を表現するために「列挙子」という仕組みが用意されています。また、Objective-C 2.0からは、「高速列挙」と呼ばれる、さらに効率の良いループの方法が導入されています。各ループ表現を順番に見ていきましょう。
これはほとんどのプログラミング言語に共通する、ごく普通のループ表現です。Objective-Cの場合はC言語のforループとなります。NSArrayやNSDictionaryをループ処理する場合、例えば以下のような記述になります。
#import <Foundation/Foundation.h> int main(void) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; int i; // NSArrayをループする NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil]; for (i = 0; i < [array count]; i++) { NSLog(@"index: %d, value: %@\n", i, [array objectAtIndex:i]); } // NSDictionaryをループする NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @"futaba", @"name", [NSNumber numberWithInt:4], @"age", @"white", @"color", nil]; NSArray *keys = [dict allKeys]; for (i = 0; i < [keys count]; i++) { NSLog(@"key: %@, value: %@\n", [keys objectAtIndex:i], [dict objectForKey:[keys objectAtIndex:i]]); } [pool drain]; return 0; }
オブジェクトを扱う集合クラスを効率よくループするためには、「列挙子」の仕組みが有利です。列挙子は、集合の要素に順番にアクセスするための仕組みです。Objective-Cでは、Foundationの「NSEnumerator」というクラスが列挙子の役割を果たします。
以下に、列挙子を使ったループの例を見てみましょう。
#import <Foundation/Foundation.h> int main(void) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // NSArrayをループする NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil]; NSEnumerator *enumerator = [array objectEnumerator]; // (a) id obj; while (obj = [enumerator nextObject]) { // (b) NSLog(@"value: %@\n", obj); } // NSDictionaryをループする NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @"futaba", @"name", [NSNumber numberWithInt:4], @"age", @"white", @"color", nil]; NSEnumerator *objEnumerator = [dict objectEnumerator]; // (a) id obj1; while (obj1 = [objEnumerator nextObject]) { // (b) NSLog(@"value: %@\n", obj1); } NSEnumerator *keyEnumerator = [dict keyEnumerator]; // (c) id key; while (key = [keyEnumerator nextObject]) { NSLog(@"key: %@, value: %@\n", key, [dict objectForKey:key]); } [pool drain]; return 0; }
NSArrayやNSDictionaryなどの集合クラスは、自身が保持する値にアクセスするための列挙子(NSEnumeratorのインスタンス)を返すメソッド「objectEnumerator」を実装しています(コード中のa)。
列挙子を取得したら、「nextObject」メソッドで集合の要素に順番にアクセスします。nextObjectは、次々と要素を進めていき、取り出すべき要素がなくなるとnilを返すため、これをwhile文の条件として利用することで、ループ処理が可能になります(コード中のb)。
また、NSDictionaryに関しては、集合の各キーにアクセスするための列挙子を返す「keyEnumerator」も実装しています(コード中のc)。
NSEnumeratorを直接利用するループ処理は、先ほどの例にもあるようにコードが少々複雑になるのが難点です。Objective-C 2.0から導入された「高速列挙」を利用すれば、よりスッキリした形で集合クラスに対するループ処理を記述することができます。以下に例を示します。
#import <Foundation/Foundation.h> int main(void) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // NSArrayをループする NSArray *array = [NSArray arrayWithObjects:@"abc", @"def", @"ghi", @"jkl", nil]; for (id obj in array) { NSLog(@"value: %@\n", obj); } // NSDictionaryをループする NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: @"futaba", @"name", [NSNumber numberWithInt:4], @"age", @"white", @"color", nil]; for (id key in dict) { NSLog(@"key: %@, value: %@\n", key, [dict objectForKey:key]); } // NSEnumeratorをループする NSEnumerator *enumerator = [dict objectEnumerator]; for (id obj in enumerator) { NSLog(@"value: %@\n", obj); } [pool drain]; return 0; }
わざわざNSEnumeratorのインスタンスを取得せずに、従来のfor文をさらに簡潔にしたような記述でループ処理を実行できます。また、ループの内部で各要素を保存するための一時変数も、for文の()内で宣言できます。
高速列挙を利用するためには、その集合クラスが、高速列挙のための共通の仕組みを実装している必要があります。このように、ある種の機能を共通の手順で利用できるようにクラスのAPIを統一させたいとき、Objective-Cでは「プロトコル」という仕組みが利用されます。
プロトコルについて詳しくは別の回で解説しますが、クラスがあるプロトコルを「採用」している場合、そのクラスは、そのプロトコルで規定されている機能(つまりメソッド)を必ず実装する必要があります。この仕組みにより、「あ、このクラスはXXXプロトコルを採用しているから、XXXの機能を利用できるな」というように、利用者側にとっても分かりやすいクラス設計が可能になります。
高速列挙の仕組みを規定しているのは、「NSFastEnumeration」というプロトコルです。NSArrayもNSDictionaryも、そしてNSEnumeratorも、このプロトコルを採用しています。
今回は値の集合の扱い方について解説しました。配列の処理は、データの量によってはプログラムのパフォーマンスにも大きく影響します。データの性質や処理内容を考慮して、効率の良いロジックが書けるように常日ごろから心掛けたいものです。
Copyright © ITmedia, Inc. All Rights Reserved.