Skip to content

KVC: Set And Array Operators @unionOfArrays

2010.08.31

KVC (Key-Value Coding) の Set And Array Operators をマスターすると、コードを簡素化できたり、NSPredicate でできることの幅が増えたり、Cocoa Bindings でできることの幅が増えるなどの利点があります。Set And Array Operators の概要から始まったシリーズ。

今回は、@unionOfArrays オペレータを取り上げます。


@unionOfArrays オペレータは、取得した配列に含まれるオブジェクトを収集して配列として返すオペレータです。

@unionOfArrays オペレータを使用する際のフォーマットは、次のようになります。

[obj valueForKeyPath:
    @"keyPathToArrayOrSet.@unionOfArrays.keyPathToArray"];

@unionOfArrays オペレータは、keyPathToArrayOrSet で得られる NSArray 或は NSSet の各要素に於いて valueForKeyPath:@”keyPathToArray” を実行し、それによって得られた配列に含まれるオブジェクトをひとつの NSArray に収集し、返します。

例えば、次のような NSDictionary 型変数 bowling があるとします。

bowling = {
    players = (
        {
            name = A;
            scores = (
                120,
                150,
                180
            );
        },
        {
            name = B;
            scores = (
                170,
                190,
                120
            );
        },
        {
            name = C;
            scores = (
                50,
                250
            );
        }
    );
}

このとき、player A, B, C 全てのスコアを持つ配列は、次のように求めることができます。

NSArray *allScores;
allScores = [bowling valueForKeyPath:@"players.@unionOfArrays.scores"];

上の例で、allScores は (120, 150, 180, 170, 190, 120, 50, 250) という配列になります。

内部的にどのような処理が行われているのかを示したものが、以下のコードです。players の各要素に於いて、valueForKeyPath:@”scores” を行い各人の scores 配列を取得し、その配列に含まれるオブジェクトを allScores に収集しています。

NSMutableArray *allScores;
NSArray *players;
players = [bowling valueForKey:@"players"];
allScores = [NSMutableArray array];
for (NSDictionary *player in players) {
    [allScores addObjectsFromArray:
        [player valueForKeyPath:@"scores"]];
}

また、上のコードは、「@unionOfArrays を使用しなかった場合、同じ結果を得るのにどれだけのコード量が必要か」を示しています。@unionOfArrays を使用した方が、簡潔に結果を求めることができることが分かります。

パフォーマンスはどうでしょうか。比較してみました。
比較には以下のコードを使用しました。使用した機種は MacBook Pro。Mac アプリとして作成し、実行しました。

@implementation unionOfArraysAppDelegate

- (NSDictionary*)bowling
{
    // Make bowling
    NSDictionary *bowling;
    NSMutableArray *players;
    players = [NSMutableArray array];
    bowling = [NSDictionary dictionaryWithObjectsAndKeys:
        players, @"players",
        nil];
    
    // Add player to players
    NSDictionary *player;
    NSArray *scores;
    for (NSInteger i = 0; i < 1000; i++) {
        // Make scores
        scores = [NSArray arrayWithObjects:
            [NSNumber numberWithUnsignedInt:arc4random()],
            [NSNumber numberWithUnsignedInt:arc4random()],
            [NSNumber numberWithUnsignedInt:arc4random()],
            nil];
        
        // Make player
        player = [NSDictionary dictionaryWithObjectsAndKeys:
            [NSString stringWithFormat:@"Name%ld", i], @"name",
            scores, @"scores",
            nil];
        
        // Add player to players
        [players addObject:player];
    }
    
    return bowling;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    CFAbsoluteTime startTime;
    CFTimeInterval sumOfTime;
    NSUInteger const count = 100;
    
    // Get bowling
    NSDictionary *bowling;
    bowling = [self bowling];
    
    // @unionOfArrays version
    sumOfTime = 0.0;
    for (int i = 0; i < count; i++) {
        // Make pool
        NSAutoreleasePool *pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        // Record startTime
        startTime = CFAbsoluteTimeGetCurrent();
        
        // Get allScores
        NSArray *allScores;
        allScores = [bowling valueForKeyPath:
            @"players.@unionOfArrays.scores"];
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
    
    // Manual version
    sumOfTime = 0.0;
    for (int i = 0; i < count; i++) {
        // Make pool
        NSAutoreleasePool *pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        // Record startTime
        startTime = CFAbsoluteTimeGetCurrent();
        
        // Get allScores
        NSMutableArray *allScores;
        NSArray *players;
        players = [bowling valueForKey:@"players"];
        allScores = [NSMutableArray array];
        for (NSDictionary *player in players) {
            [allScores addObjectsFromArray:
                [player valueForKeyPath:@"scores"]];
        }
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
}

@end

測定結果は以下の通りになりました:

@unionOfArrays version 0.000494 s
Manual version 0.000372 s

@unionOfArrays オペレータを使用した方が速度が若干遅くなる、という結果が出ました。@unionOfArrays オペレータを使用するべきかどうかはケース・バイ・ケースということですね。

次回は、@distinctUnionOfArrays を取り上げます。

Advertisements

From → Develop

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s