2013年
9月
18日
水
iOS7リリースということで、新しいクラスでも紹介してみる。
そう、それは、ジェスチャの新顔。
UIScreenEdgePanGestureRecognizer
名前の通り、デバイスのエッジからのドラッグを検知してくれるクラス。
プロパティに edges というのがあるので、ここに検知したいデバイスの方向を指定。
具体的には、これまたOS7から加わった以下の列挙型から選ぶ。
typedef NS_OPTIONS(NSUInteger, UIRectEdge) { UIRectEdgeNone = 0, UIRectEdgeTop = 1 << 0, UIRectEdgeLeft = 1 << 1, UIRectEdgeBottom = 1 << 2, UIRectEdgeRight = 1 << 3, UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight } NS_ENUM_AVAILABLE_IOS(7_0);
ばっちり、上下左右が網羅されている模様。
リファレンスにもあるように、このプロパティは相対的なものなので
アプリのオリエンテーションによって適切に方向を判定してくれる(縦で左なら、横でも左。下とか上にはならない)。
上下左右が指定可能ではあるものの、スクリーンの上と下のジェスチャには
それぞれノーティフィケーションセンターとコントロールセンターが割り当てられているので
実質的に使えるのは左と右くらいだったりはする(コントロールセンターは設定でオフにできるので、そうすれば下からのジェスチャは検知できる。上は……どうしようもない)。
なお、動作確認は実機でないと無理くさい。
3.5inch のシミュレータには一応エッジがあるので、いけるかと思ったけどダメだった。
実装したところで、気づかれない可能性もなくはないけれど
ジェスチャが足りないと感じたら使ってみるとよろしい。
で、OS7のナビゲーションコントローラが早速このジェスチャを取り込んでいたり。
これまでは左上のボタンをタップして戻るしかなかったけど、左エッジからのドラッグでも戻れるようになってる。
スクリーンが大きくなって、片手では上にあるボタンが押しにくかった不満がこれで解消。
やったね!
……でも、これに気づく人がどれだけいるのやら。
2013年
8月
24日
土
唐突だけど(そうでもない)
手動でviewのサイズ(frame)を計算するのではなく、
Autolayoutを使った方がやっぱり良い(というか使うべき)と思った。
ViewControllerのコンテナを使っていると、viewを重ねる際にその位置やサイズを
調整してやる必要がある。
親のviewに対して、子のviewをどれくらいの大きさでどこに配置するのか決める際だとか。
その時、viewDidLoad: でロードされたviewのサイズが実際に
画面に表示されるサイズと異なっていたりする。
例えば、InterfaceBuilderでコンテナviewを使って親子構造を作った場合(こんな感じ)、
子側(右)のViewControllerの viewDidLoad: の時点では、viewのサイズが親のviewであるコンテナview(左のContainer)のサイズにはなっていない。
よって、このタイミングで子viewの上にのるラベルなどのコンポーネントの位置などを手動で調整すると壮大にずれてしまう。
では、いつそのサイズが確定するかというと、viewWillLayoutSubviews: の模様。
ただし、viewWilllLayoutSubviews: が呼ばれるのは1度ではないので
その都度、調整する処理が実行されるのは無断なような気もしないではない。
だったら、viewDidLoad: でAutolayoutを設定してしまえば1回で済むし早くて楽じゃないの、という帰結。
アップルがAutolayoutを導入した経緯もそんなところにあるような気がしたり、しなかったり。
os7のリリースも近づいてるし、そろそろos5を捨ててos6以降に対応ということにすれば、Autolayoutも使えていろいろ良いことずくめ。マップもだいぶマシにはなったし。
もちろん、os7にだけ対応というのが
一番楽でもっとも望ましいのは間違いないけれど(プログラマ的に)。
なにしろタダだしね!(デバイスが対応している限りは)。
2013年
8月
12日
月
Autolayoutは便利。
特にInterfaceBuilderで設定する場合は。
コード側で設定する時も、あの文法さえ理解すればそんなに難しくはない。
最初はとっつきにくく感じるけど、結局は慣れの問題。何だってそうか。
でも、デバイスの縦と横とでレイアウトを大幅に変更したい時は少々、手を加える必要がある。
親のviewに対して絶対座標で常に指定できるのであれば、
デバイスの向きに関わらず値を調整するだけで済むけれど
各UIの要素に対して相対的な位置で並べる時はそうもいかない。
例えば、こんなレイアウト。
縦と横とで画像とlabelの位置関係が異なるため、Autolayoutも縦用と横用とを用意する必要がある。
その処理はUIViewControllerの - (void)updateViewConstraints の中に書く。
以下コード(label2の文字列が1行で収まらないことも考慮)。
- (void)updateViewConstraints { [super updateViewConstraints]; // remove first [self.view removeConstraints:self.view.constraints]; if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) { // for landscape [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-25-[_label1(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-15-[_label1]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-10-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-15-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView]-15-[_label2(220)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-10-[_label2(<=150)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView]-15-[_label3]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label3, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label2]-15-[_label3(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label3)]]; } else { // for portrait [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[_label1(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-15-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView]-15-[_label2(<=150)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_label2(290)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label2]-15-[_label3(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label3)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_label3(290)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label3)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; } }
これで縦と横とでちゃんとレイアウトが保たれる。
旧来通りの矩形計算と比べ、手間的にはそんなに変わらない気もするけれど
文字列の長さを計算したりする必要がないので、楽になったことには違いない。
堕落プログラマにはなれないものの、ものぐさプログラマにはなれる、
Autolayoutとはそんな機能、かもしれなかった。
2013年
8月
05日
月
OS5から使えるようになった、UIViewControllerのContainer。
これらを使ってUIViewControllerの親子関係が実現できるのは周知の通り。
(参考: View Controller Programming Guide for iOS)
-(void)addChildViewController:(UIViewController *)childController -(void)removeFromParentViewController -(void)willMoveToParentViewController:(UIViewController *)parent -(void)didMoveToParentViewController:(UIViewController *)parent
何が嬉しいかと言えば、以下のviewメソッド四兄弟を適宜呼び出してくれる。確かに便利。
-(void)viewWillAppear:(BOOL)animated -(void)viewWillDisappear:(BOOL)animated -(void)viewDidAppear:(BOOL)animated -(void)viewDidDisappear:(BOOL)animated
そのコンテナ構造は、以下のような関係を想定して作られている(それぞれUIViewController)。
Parent
|-- Child1
|-- Child2
|-- …
で、ParentのviewにChild1やChild2のviewがのることでUIを構成するわけだが、
この時、上記の四兄弟メソッドが呼ばれるのは、viewが追加/削除されたタイミングであって
その対象は、当然ながら、追加/削除されたviewを管理するUIViewControllerのみである。
つまり、上の親子関係であれば、ParentのviewWillAppear:等は
コンテナ関係の操作をしても呼ばれない。
これは何もParentに限ったことではなく、
Child1のviewの上にChild2のviewを追加し、その後に削除したとしても
呼ばれるのはChild2のviewWillDisappear:/viewDidDisappear:だけであり、
Child1のviewWillAppear:/viewDidAppear:は呼ばれない。
この辺りがナビゲーションなどとは挙動が異なる。
全部自動でやってくれるかと思ったら、案外そうでもなかった、良くあるパターン。
期待し過ぎであり、勘違いのせいでもあるけれど。
え? 四兄弟メソッドなんて、必要であれば自分で呼べば良いだけだって?
——それを言ったらおしまいじゃないの。
手動でやると二重に呼ばれたりして大変なことになったりもするし(経験談)。
できるだけ、元々ある仕組みを利用して楽をしたい、そんなコンセプト。
2013年
7月
31日
水
ヘッダには純粋にAPIだけを定義すれば良い。
そんな時代がもう来てた。
無名カテゴリにより、実装側(.m)にインスタンス変数やらを定義できるようになって久しいわけだが
プロトコルの宣言も全部そっちでできることに気がついた(今さら)。
#import "NSTrivial.h" @interface NSTrivial() <UIScrollViewDelegate> { UIScrollView *_scrollView; } @end @implementation @end
これまでわざわざヘッダ側に宣言していたので、ちょっとだけ便利。
あと、関係ないけどインスタンス変数の定義はここにも書ける。
@implementation { UIScrollView *_scrollView; } @end
@interfaceで定義するのとどう違うのかというと、特に何も違わない。併用も可能。
valueForKey: でアクセスできなくなるかも、と思ったけどそんなことも全然ない。
無名カテゴリを書くのが面倒な時や、気分転換したい時にでもどうぞ。
2013年
9月
18日
水
iOS7リリースということで、新しいクラスでも紹介してみる。
そう、それは、ジェスチャの新顔。
UIScreenEdgePanGestureRecognizer
名前の通り、デバイスのエッジからのドラッグを検知してくれるクラス。
プロパティに edges というのがあるので、ここに検知したいデバイスの方向を指定。
具体的には、これまたOS7から加わった以下の列挙型から選ぶ。
typedef NS_OPTIONS(NSUInteger, UIRectEdge) { UIRectEdgeNone = 0, UIRectEdgeTop = 1 << 0, UIRectEdgeLeft = 1 << 1, UIRectEdgeBottom = 1 << 2, UIRectEdgeRight = 1 << 3, UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight } NS_ENUM_AVAILABLE_IOS(7_0);
ばっちり、上下左右が網羅されている模様。
リファレンスにもあるように、このプロパティは相対的なものなので
アプリのオリエンテーションによって適切に方向を判定してくれる(縦で左なら、横でも左。下とか上にはならない)。
上下左右が指定可能ではあるものの、スクリーンの上と下のジェスチャには
それぞれノーティフィケーションセンターとコントロールセンターが割り当てられているので
実質的に使えるのは左と右くらいだったりはする(コントロールセンターは設定でオフにできるので、そうすれば下からのジェスチャは検知できる。上は……どうしようもない)。
なお、動作確認は実機でないと無理くさい。
3.5inch のシミュレータには一応エッジがあるので、いけるかと思ったけどダメだった。
実装したところで、気づかれない可能性もなくはないけれど
ジェスチャが足りないと感じたら使ってみるとよろしい。
で、OS7のナビゲーションコントローラが早速このジェスチャを取り込んでいたり。
これまでは左上のボタンをタップして戻るしかなかったけど、左エッジからのドラッグでも戻れるようになってる。
スクリーンが大きくなって、片手では上にあるボタンが押しにくかった不満がこれで解消。
やったね!
……でも、これに気づく人がどれだけいるのやら。
2013年
8月
24日
土
唐突だけど(そうでもない)
手動でviewのサイズ(frame)を計算するのではなく、
Autolayoutを使った方がやっぱり良い(というか使うべき)と思った。
ViewControllerのコンテナを使っていると、viewを重ねる際にその位置やサイズを
調整してやる必要がある。
親のviewに対して、子のviewをどれくらいの大きさでどこに配置するのか決める際だとか。
その時、viewDidLoad: でロードされたviewのサイズが実際に
画面に表示されるサイズと異なっていたりする。
例えば、InterfaceBuilderでコンテナviewを使って親子構造を作った場合(こんな感じ)、
子側(右)のViewControllerの viewDidLoad: の時点では、viewのサイズが親のviewであるコンテナview(左のContainer)のサイズにはなっていない。
よって、このタイミングで子viewの上にのるラベルなどのコンポーネントの位置などを手動で調整すると壮大にずれてしまう。
では、いつそのサイズが確定するかというと、viewWillLayoutSubviews: の模様。
ただし、viewWilllLayoutSubviews: が呼ばれるのは1度ではないので
その都度、調整する処理が実行されるのは無断なような気もしないではない。
だったら、viewDidLoad: でAutolayoutを設定してしまえば1回で済むし早くて楽じゃないの、という帰結。
アップルがAutolayoutを導入した経緯もそんなところにあるような気がしたり、しなかったり。
os7のリリースも近づいてるし、そろそろos5を捨ててos6以降に対応ということにすれば、Autolayoutも使えていろいろ良いことずくめ。マップもだいぶマシにはなったし。
もちろん、os7にだけ対応というのが
一番楽でもっとも望ましいのは間違いないけれど(プログラマ的に)。
なにしろタダだしね!(デバイスが対応している限りは)。
2013年
8月
12日
月
Autolayoutは便利。
特にInterfaceBuilderで設定する場合は。
コード側で設定する時も、あの文法さえ理解すればそんなに難しくはない。
最初はとっつきにくく感じるけど、結局は慣れの問題。何だってそうか。
でも、デバイスの縦と横とでレイアウトを大幅に変更したい時は少々、手を加える必要がある。
親のviewに対して絶対座標で常に指定できるのであれば、
デバイスの向きに関わらず値を調整するだけで済むけれど
各UIの要素に対して相対的な位置で並べる時はそうもいかない。
例えば、こんなレイアウト。
縦と横とで画像とlabelの位置関係が異なるため、Autolayoutも縦用と横用とを用意する必要がある。
その処理はUIViewControllerの - (void)updateViewConstraints の中に書く。
以下コード(label2の文字列が1行で収まらないことも考慮)。
- (void)updateViewConstraints { [super updateViewConstraints]; // remove first [self.view removeConstraints:self.view.constraints]; if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation)) { // for landscape [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-25-[_label1(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-15-[_label1]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-10-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-15-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView]-15-[_label2(220)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-10-[_label2(<=150)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label1)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView]-15-[_label3]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label3, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label2]-15-[_label3(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label3)]]; } else { // for portrait [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[_label1(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label1]-15-[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label1, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_imageView(240)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_imageView]-15-[_label2(<=150)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _imageView)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_label2(290)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_label2]-15-[_label3(21)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label2, _label3)]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[_label3(290)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_label3)]]; [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; } }
これで縦と横とでちゃんとレイアウトが保たれる。
旧来通りの矩形計算と比べ、手間的にはそんなに変わらない気もするけれど
文字列の長さを計算したりする必要がないので、楽になったことには違いない。
堕落プログラマにはなれないものの、ものぐさプログラマにはなれる、
Autolayoutとはそんな機能、かもしれなかった。
2013年
8月
05日
月
OS5から使えるようになった、UIViewControllerのContainer。
これらを使ってUIViewControllerの親子関係が実現できるのは周知の通り。
(参考: View Controller Programming Guide for iOS)
-(void)addChildViewController:(UIViewController *)childController -(void)removeFromParentViewController -(void)willMoveToParentViewController:(UIViewController *)parent -(void)didMoveToParentViewController:(UIViewController *)parent
何が嬉しいかと言えば、以下のviewメソッド四兄弟を適宜呼び出してくれる。確かに便利。
-(void)viewWillAppear:(BOOL)animated -(void)viewWillDisappear:(BOOL)animated -(void)viewDidAppear:(BOOL)animated -(void)viewDidDisappear:(BOOL)animated
そのコンテナ構造は、以下のような関係を想定して作られている(それぞれUIViewController)。
Parent
|-- Child1
|-- Child2
|-- …
で、ParentのviewにChild1やChild2のviewがのることでUIを構成するわけだが、
この時、上記の四兄弟メソッドが呼ばれるのは、viewが追加/削除されたタイミングであって
その対象は、当然ながら、追加/削除されたviewを管理するUIViewControllerのみである。
つまり、上の親子関係であれば、ParentのviewWillAppear:等は
コンテナ関係の操作をしても呼ばれない。
これは何もParentに限ったことではなく、
Child1のviewの上にChild2のviewを追加し、その後に削除したとしても
呼ばれるのはChild2のviewWillDisappear:/viewDidDisappear:だけであり、
Child1のviewWillAppear:/viewDidAppear:は呼ばれない。
この辺りがナビゲーションなどとは挙動が異なる。
全部自動でやってくれるかと思ったら、案外そうでもなかった、良くあるパターン。
期待し過ぎであり、勘違いのせいでもあるけれど。
え? 四兄弟メソッドなんて、必要であれば自分で呼べば良いだけだって?
——それを言ったらおしまいじゃないの。
手動でやると二重に呼ばれたりして大変なことになったりもするし(経験談)。
できるだけ、元々ある仕組みを利用して楽をしたい、そんなコンセプト。
2013年
7月
31日
水
ヘッダには純粋にAPIだけを定義すれば良い。
そんな時代がもう来てた。
無名カテゴリにより、実装側(.m)にインスタンス変数やらを定義できるようになって久しいわけだが
プロトコルの宣言も全部そっちでできることに気がついた(今さら)。
#import "NSTrivial.h" @interface NSTrivial() <UIScrollViewDelegate> { UIScrollView *_scrollView; } @end @implementation @end
これまでわざわざヘッダ側に宣言していたので、ちょっとだけ便利。
あと、関係ないけどインスタンス変数の定義はここにも書ける。
@implementation { UIScrollView *_scrollView; } @end
@interfaceで定義するのとどう違うのかというと、特に何も違わない。併用も可能。
valueForKey: でアクセスできなくなるかも、と思ったけどそんなことも全然ない。
無名カテゴリを書くのが面倒な時や、気分転換したい時にでもどうぞ。