«

»

3月
22

[objective-c][KVC]キー値コーディングについて調べたら面白かった

キー値コーディング (KVC) とは何か?

objective-cはiPhoneが流行りだした頃に少しHowTo本を写経してみただけで全く手つかずだったのですが、必要に迫られて先月からものすごい周回遅れで勉強しています。とりあえず今のところ、サッパリ良さが分かっていない(笑)が、おそらくその印象の最大の要因はファッション的な問題でしょう。雑感として、良い所は、言語仕様が小さい。その割に動的型付け言語っぽい柔軟さがあるところでしょうか。言い換えると、動的言語っぽい柔軟さがあるから言語仕様が小さくて済んでいるのかもしれない。反面、各種フレームワークの流儀や慣習を押さえないとうまくいかない。また、objective-c自体もそうですがXcodeにしろFoundationにしろ調べれば調べるほど 数年でものすごい変わってしまう ようなので調べた情報が古かったりとキャッチアップするのがしんどい部分もあります。このあたりはしかたがないので多少の歴史的経緯を踏まえつつ一つ一つ潰していくしかありません。

で、本題はと言うと、KVCです。KVC、耳慣れない単語です。とりあえず最初はスルーしました。多分デザインパターンみたいなものだろうとは思いました。でもそういったものは抽象的なので総じて分かりづらいです。(WindowsのWPFを覚え始めた時、最初はMVVMをスルーしました。数ヶ月後に謎が解けました。)いろいろとドキュメントを見ていくと KVC やら KVO という謎の単語が頻出します。それでもスルーしました。そうしているうちに、「そういえばオブザーバーパターン的なものを実装する場合、Cocoaでは一般的にどうやるんだろうか???」と思っていたら、他でもない、それがフレームワークでサポートしているオブザーバーパターン実装、KVC + KVO + KVB なんですね。少し前進した気がします。(他にもNSNotification、デリゲート、ターゲットアクションが通知の仕組みとして考えられる)

参考)ダイナミックObjective-C (111) デザインパターンをObjective-Cで – Observer (3) | マイナビニュース http://news.mynavi.jp/column/objc/111/

今回はその1つのKVCについて掘り下げます。(掘り下げますが、オブザーバーパターンの話からは大きく脱線してしまっています。)

apple公式の キー値コーディング プログラミングガイド
https://developer.apple.com/jp/devcenter/ios/library/documentation/KeyValueCoding.pdf

によると、KVCとは、

キー値コーディングとは、オブジェクトのプロパティに間接的にアクセスするための仕組みです。アクセサメソッドを呼び出してアクセスしたり、インスタンス変数としてアクセスするのではなく、プロパティの識別に文字列を使用してアクセスします。キー値コーディングでは、アクセサメソッドの実装パターンやシグニチャ(引数並び)の規約を定めています。

と、解説されています。よくわからないがダックタイピングみたいなもの??でしょうか。
ひとまず簡単なコードに落としてみます。普通のアクセサメソッドを使ったアクセス方法から発展させると次のように名前( キー もしくは キーパス )でアクセスできることがわかります。

// 適当にこんな感じのクラスがあったとして…
@interface Hoge : NSObject
@property int amount;
@property Hoge* child;
@end
 
Hoge* hoge = Hoge.alloc.init;
hoge.child = Hoge.alloc.init;
 
// 1. 普通のアクセサメソッドを利用した値の設定・取得
hoge.amount = 1;
NSLog(@"%d", hoge.amount);
 
// 2. KVC ( NSKeyValueCodingプロトコル ) を利用した値の設定・取得
 
// i. キーを使った値の設定・取得
[hoge setValue:@2 forKey:@"amount"];
NSLog(@"%@", [hoge valueForKey:@"amount"]);
 
// ii. キーパスを使った値の設定・取得 (obj-cのドット記法を使って辿る)
[hoge setValue:@50 forKeyPath:@"child.amount"];
NSLog(@"%@", [hoge valueForKeyPath:@"child.amount"]);
 
// note) forKeyPathは キーパスだけでなくキーでも取得可能
[hoge setValue:@20 forKeyPath:@"amount"];
NSLog(@"%@", [hoge valueForKeyPath:@"amount"]);

これだけであればかえって冗長になりメリットが感じられないが、「きっとそのうち有り難くなるにちがいない。」と考えることにします。 キーキーパス の定義が曖昧になる時は先のオフィシャルのドキュメントを確認すること。

コレクション演算子

コレクション演算子は valueForKeyPath: メソッドに与えるキーパス内で使える特別な演算子で記法は次のとおりになります。

keypathToCollection.@collectionOperator.keypathToProperty

このうち、演算子より左にあるものを Left KeyPath 、右にあるものを Right KeyPath と言います。左に配列や集合などのコレクションを置き、右に演算につかうプロパティを指定する。というのが基本的な使い方で、なんとなくイテレータとブロック、クロージャとかそういう類を彷彿とさせる、このコレクション演算子というのは直感的にかなり野心的な使い方ができそうな気がプンプンしますが、

現状では、独自のコレクション演算子を定義することはできません。

と、記載されています。

念のため、こちらも…

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html

やっぱり、

Note: It is not currently possible to define your own collection operators.

ダメです。ダメなんですかね〜。だとしたら少し中途半端なんじゃないだろうか。。。

単純型コレクション演算子

単純型コレクション演算子は、右キーパスで指定したプロパティに対して作用します。次の例のように、@count以外は全て右キーパスが必要です。(次の例ではリテラルを使って生成した簡単なNSNumberの配列のため、selfとしていますが、実際はプロパティがいくつかある複合的なオブジェクトを扱うと思いますので、selfではなくなんらかのプロパティ名を与えることが多いはすです。)

NSArray* arr = @[ @1, @50, @13, @42, @98, @4 ];
NSLog(@"サンプル数 %@", [arr valueForKey:@"@count"]);
NSLog(@"合計 %@", [arr valueForKeyPath:@"@sum.self"]);
NSLog(@"平均 %@", [arr valueForKeyPath:@"@avg.self"]);
NSLog(@"Max %@", [arr valueForKeyPath:@"@max.self"]);
NSLog(@"Min %@", [arr valueForKeyPath:@"@min.self"]);

と、このように簡単な集計関数として使えます。( 最初は@sum.intValueなどとしていましたが、selfでもできるようです。)

オブジェクト演算子

オブジェクト演算子は、右キーパスが単一のコレクションに作用して、配列として結果を返します。

//オブジェクト演算子
{
    NSArray* arr = @[ @"apple", @"orange", @"strawberry" ];
    NSLog(@"右キーパスの値を連結して配列化 %@",
    [arr valueForKeyPath:@"@unionOfObjects.self.length"]);
}
{
    NSArray* arr = @[ @"apple", @"orange", @"strawberry", @"apple" ];
    NSLog(@"右キーパスの値を連結して重複なしで配列化 %@",
    [arr valueForKeyPath:@"@distinctUnionOfObjects.self"]);
}
 
// 配列演算子、集合演算子
{
    NSArray* arr = @[ @[@"c",@"c++",@"obj-c",@"java",@"c#"], @[@"ruby",@"python",@"javascript"], @[@"lisp",@"haskell"] ];
    NSLog(@"右キーパスの配列を連結して配列化 %@",
    [arr valueForKeyPath:@"@unionOfArrays.self"]);
}
{
    NSArray* arr = @[ @[@"foo"], @[@"foo",@"bar"], @[@"bar",@"baz"] ];
    NSLog(@"右キーパスの配列を連結して重複なしで配列化 %@",
    [arr valueForKeyPath:@"@distinctUnionOfArrays.self"]);
}
#define $(...)  [NSSet setWithArray:@[__VA_ARGS__]]
{
    NSArray* arr = @[ $(@"foo"), $(@"foo",@"bar"), $(@"bar",@"baz") ];
    NSLog(@"右キーパスの集合を連結して配列化 %@",
    [arr valueForKeyPath:@"@unionOfSets.self"]);
    NSLog(@"右キーパスの集合を連結して重複なしで配列化 %@",
    [arr valueForKeyPath:@"@distinctUnionOfSets.self"]);
}

※一般に禁則のマクロを使っているのは、集合には現在のところ配列や辞書のようなリテラルがないためで、実際にはあまり使わないほうが行儀が良いと思いますが、ここではそのほうが他との類似性が分かりやすいので使っています。

なお、先ほどのコレクション演算子の独自な定義については、

KBInitializeCollectionExtensions(); // おまじないが必要
NSArray* arr = @[ @"iPhone5s", @"iPhone3gs", @"pipin@" ];
NSLog(@"%@" , [arr valueForKeyPath:@"[collect].{ self contains[c] 'iphone' }.self"] );

このような形で抽出条件を与えられるよう拡張してしまったものもあります。

Collection Extensions | kickingbear
http://kickingbear.com/blog/archives/9

紹介記事
KVC Collection Operators : NSHipster
http://nshipster.com/kvc-collection-operators/

議論
Fun with KVC — Noodlings
http://www.noodlesoft.com/blog/2009/06/30/fun-with-kvc/

この拡張はかなり面白いと思うのですが、紹介記事やソースコメントにswizzleという単語が使われていて、
Pointer swizzling – Wikipedia, the free encyclopedia http://en.wikipedia.org/wiki/Pointer_swizzling
なんとなくこれは業務として使ってはアカンやつやという気がしています。

一方で、appleがC標準として提案しているブロックを使ってコレクション操作を行う
underscore.jsのobjective-c実装といった感じの

Underscore.m http://underscorem.org/

はとても魅力的です。これ、かなり良さ気ですね。
やっぱりKVCの方は割り切って使うべきなんでしょうか。

ということでかなりお腹いっぱいになってしまったため、KVOに結びつかない話で終わります。

追記 本来使うべき局面はこちら(Cocoa bindings)

コメントを残す

メールアドレスは公開されません

次の HTMLタグおよび属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>