読者です 読者をやめる 読者になる 読者になる

ほげほげ(仮)

仮死状態

KVC Collection Operators が便利っぽい

Objective-C

Modernize your Objective-C // Speaker Deck のスライドを読んで初めて知ったので、調べてみたら便利そうだったのでそれのメモです

公式ドキュメント Key-Value Coding Programming Guide: Collection Operators

概要

コレクションのvalueForKeyPathメソッドで特殊演算子を文字列で指定することで合計や平均を取得することができます。

演算子@ から始まります。

なんか言葉で説明するのが苦手なのでサンプルをだらだらと書いておきます。

Simple Collection Operators

例として次のような配列があるとします

// 数値配列
NSArray *numbers = @[@3, @2, @5, @1, @4];

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"YYYY/MM/dd"];
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"]];

// 日付配列
NSArray *dates = @[[formatter dateFromString:@"2014/03/01"],
                   [formatter dateFromString:@"2014/02/01"],
                   [formatter dateFromString:@"2014/05/01"],
                   [formatter dateFromString:@"2014/01/01"],
                   [formatter dateFromString:@"2014/04/01"]];

// プロパティ name と age を持っているオブジェクト
Person *p1 = [[Person alloc] initWithName:@"hoge" age:18];
Person *p2 = [[Person alloc] initWithName:@"foo" age:23];
Person *p3 = [[Person alloc] initWithName:@"piyo" age:16];

// オブジェクト配列
NSArray *people = @[p1, p2, p3];

@count

配列件数を取得

[numbers valueForKeyPath:@"@count"]; // 5
[dates valueForKeyPath:@"@count"]; // 5
[people valueForKeyPath:@"@count"]; // 3

@avg

平均値を取得

[numbers valueForKeyPath:@"@avg.self"]; // 3
[dates valueForKeyPath:@"@avg.self"]; // 実行時エラー
[people valueForKeyPath:@"@avg.age"]; // 19

@演算子.プロパティのように記述する。単純な配列はプロパティにselfを指定。

@max

最大値を取得

[numbers valueForKeyPath:@"@max.self"]; // 5
[dates valueForKeyPath:@"@max.self"]; // 2014/05/01
[people valueForKeyPath:@"@max.age"]; // 23

@min

最小値を取得

[numbers valueForKeyPath:@"@min.self"]; // 1
[dates valueForKeyPath:@"@min.self"]; // 2014/01/01
[people valueForKeyPath:@"@min.age"]; // 16

@sum

合計を取得

[numbers valueForKeyPath:@"@sum.self"]; // 15
[dates valueForKeyPath:@"@sum.self"]; // 実行時エラー
[people valueForKeyPath:@"@sum.age"]; // 57

Object Operators

サンプル配列

// プロパティ name と age を持っているオブジェクト
Person *p1 = [[Person alloc] initWithName:@"hoge" age:18];
Person *p2 = [[Person alloc] initWithName:@"foo" age:23];
Person *p3 = [[Person alloc] initWithName:@"piyo" age:16];
Person *p4 = [[Person alloc] initWithName:@"piyo" age:27];

// オブジェクト配列
NSArray *people = @[p1, p2, p3, p4];

@distinctUnionOfObjects

指定したプロパティの配列を取得

  • 重複は除外される
  • 順番は不確定っぽい(?)
[people valueForKeyPath:@"@distinctUnionOfObjects.name"]; // @[@"foo", @"hoge", @"piyo"]

@unionOfObjects

指定したプロパティの配列を取得

  • 重複は除外しない
  • 順番は不確定っぽい(?)
[people valueForKeyPath:@"@unionOfObjects.name"]; // @[@"hoge", @"foo", @"piyo", @"piyo"]

Array and Set Operators

サンプル配列

// 配列の中に更に配列
NSArray *arrays = @[@[@1, @2],
                   @[@3, @4],
                   @[@2, @4]];

// 配列の中にNSSet
NSArray *sets = @[
                  [[NSSet alloc] initWithObjects:@1, @2, nil],
                  [[NSSet alloc] initWithObjects:@3, @4, nil],
                  [[NSSet alloc] initWithObjects:@2, @4, nil],
                  ];

@distinctUnionOfArrays

中にある配列を結合した配列を取得

  • 重複は除外される
  • 順番は不確定っぽい(?)
[arrays valueForKeyPath:@"@distinctUnionOfArrays.self"]; // @[@3, @2, @1, @4];

@unionOfArrays

中にある配列を結合した配列を取得

  • 重複は除外しない
  • 順番は不確定っぽい(?)
[arrays valueForKeyPath:@"@unionOfArrays.self"]; // @[@1, @2, @3, @4, @2, @4]

@distinctUnionOfSets

中にあるNSSetを結合した配列を取得

  • 重複は除外される
  • 順番は不確定っぽい(?)
[sets valueForKeyPath:@"@distinctUnionOfSets.self"]; // @[@3, @2, @1, @4]

@unionOfSets

中にあるNSSetを結合した配列を取得

  • 重複は除外しない
  • 順番は不確定っぽい(?)
[sets valueForKeyPath:@"@unionOfSets.self"]; // @[@1, @2, @3, @4, @4, @2]

NSDictionary

NSDictionary に対しても同じ感じのことができます。

キー名をうまく指定する感じです。

NSDictionary *dic = @{
                      @"key": @[
                                  @{@"num": @1},
                                  @{@"num": @2},
                              ]
                      };

NSLog(@"%@", [dic valueForKeyPath:@"key.@sum.num"]); // 3

まとめ

これを使えば例えば合計値や最大値を求めるのにループを書かなくて良いのでだいぶ楽になると思います。

さらに文字列で指定するのも面倒なのでクラスを拡張してメソッドを作っておくと更に楽かもです。

// NSArray+Operation.h
#import <Foundation/Foundation.h>

@interface NSArray (Operation)

- (NSNumber *)sum;

- (NSNumber *)max;

@end


// NSArray+Operation.m
#import "NSArray+Operation.h"

@implementation NSArray (Operation)

- (NSNumber *)sum
{
    return [self valueForKeyPath:@"@sum.self"];
}

- (NSNumber *)max
{
    return [self valueForKeyPath:@"@sum.self"];
}

@end

他にも Modernize your Objective-C // Speaker Deck には色々書いてあるので一度読んでおくと良いと思います。