2013年5月29日水曜日

Objective-C の新文法 (modern syntax) について


Objective-C の仕様は近年急激に変化しています.

  • Objective-C 2.0 (2006, Leopard)
  • Objective-C 2.0 with Blocks (2009, Snow Leopard)
  • Modern Objective-C (2011, Lion)

また,ランタイムのほうも2005年のTigerで一段落したかと思いきや,その後ガーベージコレクタを入れたり非推奨にしたりとめまぐるしく仕様が変わっています.ここらへんは OS X だけでなく iOS とも歩調を合わせるために致し方の無いことかもしれません.

さて,Lionから導入された Objective-C の modern syntax ですが,この新文法を使うことにより非常に簡潔にプログラムが書けるようになりました.

極端な例を挙げますと,

#import <Foundation/Foundation.h>

@interface PointObject: NSObject
@property float x;
@property float y;
@end

@implementation PointObject
@end

これだけで,2次元ベクトルクラスが書けます.次のように使います.

int main() {
  PointObject *p = [[PointObject alloc] init];
  p.x = 100;
  p.y = 200;
  NSLog(@"x: %f, y: %f", p.x, p.y);
  return 0;
}

もちろん Key-Value-Observing に対応します.元の PointObject クラスに一切触れること無く,プロパティの変更を外部に通知させることが出来ます.

#import <Foundation/Foundation.h>

@interface PointObject: NSObject
@property float x;
@property float y;
@end

@implementation PointObject
@end

@interface PointObserver: NSObject
@end

@implementation PointObserver
- (void)observeValueForKeyPath: (NSString *)keyPath
                      ofObject: (id)object
                        change: (NSDictionary *)change
                       context: (void *)context
{
  PointObject *p = (PointObject *)object;
  NSLog(@"x: %f", p.x);
}
@end

int main() {
  PointObject *p = [[PointObject alloc] init];
  PointObserver *o = [[PointObserver alloc] init];
  [p addObserver: o 
      forKeyPath: @"x"
         options: NSKeyValueObservingOptionInitial
         context: nil];
  p.x = 100;
  p.y = 200;
  NSLog(@"x: %f, y: %f", p.x, p.y);
  return 0;
}

実行結果は次のようになります.

2013-05-28 14:29:05.719 test[11088:707] x: 0.000000
2013-05-28 14:29:05.721 test[11088:707] x: 100.000000
2013-05-28 14:29:05.721 test[11088:707] x: 100.000000, y: 200.000000

2013年5月10日金曜日

Core Animation レイヤーに setNeedsDisplay メッセージを送っても drawLayer: inContext: メソッドが起動しない?

脱線続きですが,最近はまったもう一つの例を挙げます.皆さんのコーディング時間を無駄にしないように.

OS X で Core Animation を使ったビューをプログラムしているとします.OS X は iOS とは違いデフォルトではビューの Core Animation がオフになっていますが,xibエディタでビューの Core Animation Layer チェックボックスをオンにするか,ビューに setWantsLayer: メッセージを送ることで Core Animation をオンにできます.

その後,ビュー(NSViewのサブクラス)の更新はビューの drawRect: メソッドではなく,レイヤー(CALayer かそのサブクラス)のデリゲートの drawLayer: inContext: メソッドを使います.また,このメソッドを起動するにはレイヤーに対し setNeedsDisplay メッセージを送ります.(NSView の場合はメッセージにYESというパラメタが必要でしたが,CALayer の場合はパラメタ無しです.)

ただし,レイヤーの大きさがゼロの場合,レイヤーはどれだけ setNeedsDisplay メッセージを受け取っても drawLayer: inContext: メソッドを起動しません.そこで,レイヤーを作成したら必ずframeプロパティを設定しておきましょう.もしビューの中で設定するのであれば,

layer.frame = (CGRect)self.frame;

とすればよいでしょう.CALayer の frame プロパティは CGRect 型,NSView の frame プロパティは NSRect 型ですが,この両者は互換性があるため,そのまま代入できます.