Skip to content

KVC: Set And Array Operators @unionOfSets

2010.09.11

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

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


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

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

[obj valueForKeyPath:
    @"keyPathToArrayOrSet.@unionOfSets.keyPathToSet"];

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

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

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

* scores は NSSet です

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

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

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

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

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

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

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

@implementation unionOfSetsAppDelegate

- (NSDictionary*)bowling
{
    // Make players
    NSMutableArray *players;
    players = [NSMutableArray array];
    
    // Add player to players
    NSDictionary *player;
    NSSet *scores;
    for (NSInteger i = 0; i < 1000; i++) {
        // Make scores
        scores = [NSSet setWithObjects:
            [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];
    }
    
    // Make bowling
    NSDictionary *bowling;
    bowling = [NSDictionary dictionaryWithObject:players forKey:@"players"];
    
    return bowling;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    CFAbsoluteTime startTime;
    CFTimeInterval sumOfTime;
    NSUInteger const count = 100;
    
    // Get bowling
    NSDictionary *bowling;
    bowling = [self bowling];
    
    // @unionOfSets 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.@unionOfSets.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 valueForKeyPath:@"players"];
        allScores = [NSMutableArray array];
        for (NSDictionary *player in players) {
            [allScores addObjectsFromArray:[[player valueForKeyPath:@"scores"] allObjects]];
        }
        
        // Update sumOfTime
        sumOfTime += (CFAbsoluteTimeGetCurrent() - startTime);
        
        // Release pool
        [pool release];
    }
    NSLog(@"time average = %f", sumOfTime / (double)count);
}

@end

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

@unionOfSets version 0.000983 s
Manual version 0.000714 s

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

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

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