Objective-C Advent Calendar 2012の12/11の記事です。
最近どの言語でもシーケンスに対する処理ライブラリが充実しています。Objective-Cにも列挙や列挙を行うための仕組みも用意されているので.netと同じようなアプローチでLinq的なメソッドを実装してみました。
Linqって?
.netが持っているコレクションに対する操作をSQLチックに書けるコンパイラサポートの事なんですがここではコレクションに対する一連の操作メソッドという広義のLinqの方を取り上げます。
で、何が出来るかというとシーケンスに対するフィルタや変換、処理適用なんかがループ的な構文を使用する事なく記述できます。
例としてあるNSArrayからNSStringのものだけをNSArrayにするケースを考えてみます。
まぁ普通に考えると列挙して新しい配列に足し込んでってのが思いつきます。こんな感じに
NSMutableArray *stringArray = [[NSMutableArray alloc]init];
NSMutableArray *result = [[NSMutableArray alloc]init];
for (id obj in [stringArray objectEnumerator]) {
if ([obj isKindOfClass:[NSString class]]) {
[result addObject:obj];
}
}
return [NSArray arrayWithArray:result];
特に難しい事もないと思います。で、これをここで公開しているLinq for Objective-C(以下Linq)で書くとこんな感じ
return [[[stringArray objectEnumerator]ofClass:[NSString class]]toArray];
ずいぶんとすっきりしたと思います。
他にもフィルタをかけるwhere、変換を行うselectなんかがあります。ありそうな例としてNSDictionaryとして受信したjsonがあったとします。それをフィルタをかけながら独自型に変換するようなイメージで書くと…
NSArray *shops = [json objectForKey:@"shops"];
return [[[shops objectEnumerator]
select:^id(id shop) {
NSDictionary *shopDic = shop;
ShopData *result = [[ShopData alloc]init];
result.id = [json objectForKey:@"id"];
result.name = [json objectForKey:@"name"];
result.genre = [json objectForKey:@"genre"];
result.coordinate = CLLocationCoordinate2DMake([[json objectForKey:@"latitude"] doubleValue], [[json objectForKey:@"longitude"] doubleValue]);
result.image_url = [json objectForKey:@"image_url"];
return result;
}]where:^BOOL(id item) {
return [(ShopData *)shop.genre isEqual:@"イタリアン"];
}]toArray];
こんな感じになります。
まぁほとんど変換の部分が冗長なのでinitWithJSON:(NSDictionary*)json見たいなメソッドを作ってやればすっきりすると思います。
他にもいろいろなメソッドの組み合わせでフィルタをかける事が出来ますが冗長になってしまう場合や処理を自分で書きたい場合はCustomEnumeratorという列挙処理のヘルパがありますので処理をブロックで渡す事で独自の列挙処理を行う事が出来ます。
改行コードで一行ずつ取り出す例。マルチパートヘッダなど一気にNSStringに出来ないようなケースもあると思います。その場合はNSDataを直で列挙するobjectEnumeratorもカテゴリで追加しているのでcharでの処理も可能です。
- (NSEnumerator *)headers:(NSEnumerator *)src {
return [[CustomEnumerator alloc]initWithFunction:src nextObjectBlock:^id(NSEnumerator *_src) {
NSData * headData = [[_src takeWhile:^BOOL(id charNum) {
char charByte = [( NSNumber*)charNum charValue];
return (charByte != '\r');
}]
toData];
//最後まで読んでいるかチェックする。
if ([[_src take:1]singleOrNil] == nil){
//改行なく最後まで読んでいる場合は今回の解析は見送る。
//バッファに今のデータを追加する。
bufferData = [[NSMutableData alloc]initWithData:headData];
return nil;
}
return [[NSString alloc]initWithData:headData encoding:NSUTF8StringEncoding];
}];
}
これらの処理メソッドですがもう一つの特徴として遅延処理があげられます。どういう事かというとフィルタや選択の各種メソッドは呼んだだけでは処理が登録されるのみで最終的にforinで列挙する際やtoXXXメソッドで変換する際に処理される事になります。
(toArray、toMutableArray、toDictionary、toMutableDictionary、toData、toStringなどいろいろな処理があります)
なので通常こういった処理を分割した際にありがちなバッファのコピーが余計に発生する事はないためメモリには優しい処理となる可能性もあります。 色々とメソッドがあってややこしいですがネットでもいろんな言語でシーケンス処理の例はありますのでそれらの処理をうまくObjective-Cに持って来れると思います。
また、ちょっとしたループ処理でも書けばバグの可能性は出てくるのでやりたい処理に注力できるこういったシーケンス処理ライブラリをぜひ使ってみませんか?(と、宣伝で締めてみる)
それでわ~