Skip to content

KVC: Set And Array Operators @unionOfObjects

2010.08.29

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

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


@unionOfObjects オペレータは、オブジェクトを収集して配列を返すオペレータです。

@unionOfObjects オペレータを使用する際のフォーマットは、Set And Array Operators の概要 で説明した通り、次のようになります。

[obj valueForKeyPath:
    @"keyPathToArrayOrSet.@unionOfObjects.keyPathToProperty"];

@unionOfObjects オペレータは、keyPathToArrayOrSet で得られる NSArray 或は NSSet の各要素に於いて valueForKeyPath:@”keyPathToProperty” を実行し、それによって得られたオブジェクトをひとつの 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 それぞれのスコア平均を格納した配列は、@avg オペレータも使用して、次のように求めることができます。

NSArray *averages;
averages = [bowling
    valueForKeyPath:@"players.@unionOfObjects.scores.@avg.self"];

上の例で、averages は (150, 160, 150) という配列になります。

内部的にどのような処理が行われているのかを示したものが、以下のコードです。players の各要素に於いて、valueForKeyPath:@”scores.@avg.self” でスコア平均を取得し、それを averages に収集しています。

NSMutableArray *averages;
NSArray *players;
NSNumber *average;
players = [bowling valueForKey:@"players"];
averages = [NSMutableArray array];
for (NSDictionary *player in players) {
    average = [player valueForKeyPath:@"scores.@avg.self"];
    if (average) {
        [averages addObject:average];
    }
}

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

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

@implementation unionOfObjectsAppDelegate

- (NSDictionary*)bowling
{
    // Make bowling
    NSDictionary *bowling;
    NSMutableArray *players;
    players = [NSMutableArray array];
    bowling = [NSDictionary dictionaryWithObject:players forKey:@"players"];
    
    // 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];
    
    // @unionOfObjects version
    sumOfTime = 0.0;
    for (int i = 0; i < count; i++) {
        // Make pool
        NSAutoreleasePool *pool;
        pool = [[NSAutoreleasePool alloc] init];
        
        // Record startTime
        startTime = CFAbsoluteTimeGetCurrent();
        
        // Get averages
        NSArray *averages;
        averages = [bowling valueForKeyPath:
            @"players.@unionOfObjects.scores.@avg.self"];
        
        // 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 averages manually
        NSMutableArray *averages;
        NSArray *players;
        NSNumber *average;
        players = [bowling valueForKey:@"players"];
        averages = [NSMutableArray array];
        for (NSDictionary *player in players) {
            average = [player valueForKeyPath:@"scores.@avg.self"];
            if (average) {
                [averages addObject:average];
            }
        }
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
}

@end

測定結果は以下の通りになりました (3回の平均):

@unionOfObjects version 0.025334 s
Manual version 0.024946 s

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

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

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