2009年5月31日日曜日

NSTimerは基本的にretainせずassignでよい

NSTimerを初めて使ってみたのでハマったところをメモしておきます。


■NSTimerはNSRunLoopにretainされる。NSTimerは引数targetで与えられたオブジェクトをretainする。
いちばんハマったのがこの挙動です。
AppleのNSTimerについての公式ドキュメント(http://www.devworld.apple.com/documentation/Cocoa/Conceptual/Timers/Articles/usingTimers.html#//apple_ref/doc/uid/20000807-CJBJCBDE)にもクラスリファレンスにもきちんとと明記されていたのですが・・・思いっきり見落としてました。

これらがいったいどんな問題を引き起こすか。
たとえば普通のクラスと同じ感覚でdealloc中にNSTimerのinvalidateを呼び出すコードを書くと、永遠にdeallocが呼び出されなくなってしまいます。
// ViewControllerがNSTimerを使っているとして・・・
- (void)viewDidLoad
{
timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self // このselfはretainされる
selector:@selector(timerFired)
userInfo:nil
repeats:YES];
}
- (void)dealloc
{
[displayLabel release];
[timerLabel release];
// ここでinvalidateしてはいけない、永遠にdealloc自体が呼び出されなくなる
[timer invalidate];
[timer release];
}

なぜなら、
  • NSTimerがdeallocを呼び出すオブジェクトをretainしている、したがってNSTimerがreleaseされるまでdeallocが呼ばれない
  • NSTimerはNSRunLoopにretainされている、従ってNSTimerのinvalidateが実行されるまではNSTimerはreleaseされない
  • invalidateを呼び出しているのはこのdeallocの中以外にない。・・・詰みました。
こうならないようにするためには、dealloc以外の箇所から、適切にinvalidateメソッドを呼び出してやる必要があります。ということで、ViewControllerの中でNSTimerを使うときは、以下の2点に気をつければ良さそうです。
  1. NSTimerのオブジェクトは基本retainしない(自分でNSRunLoopにaddTimerとかしたい場合は別として)
  2. 通常deallocのタイミングでオブジェクトをreleaseするのと同じようにNSTimerをinvalidateしたい場合は、- (void)viewWillDisappear:(BOOL)animated を使う

- (void)viewWillDisappear:(BOOL)animated
{
if (timer)
{
// ここでタイマーをinvalidateする
// invalidateするとNSRunLoopがretainされていたこのタイマーをreleaseしてくれる
[timer invalidate];
timer = nil;
}
}

■userInfoはただのNSDictionary
そのまんまです。クラスリファレンスを見ても
The user info the new timer.

This parameter may be nil.
としか書いてなくて困ったのですが、本当にただのNSDictionaryです。適当に使ってくれってことでしょうか。


■次のタイマーイベントまでの間隔をリセットしたいときはsetFireDate:
たとえば何らかの理由でタイマーのFireイベントに登録していたセレクタを自分で呼び出しちゃって、次のタイマーイベントまでの間隔をリセットしたいときなどは以下のような具合にするとよいです。
if (timer)
{
// timerのsetFireDateに、次にタイマーイベントが発生する日時をセットする
// NSDateに、dateWithTimeIntervalSinceNowという便利なメソッドがあるのでこれを使う
[timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:interval]];
}

■っていうか
今このBlogを書くためにNSTimerでぐぐったらここに書いてあるようなことがいろいろ見つかってしまいました・・・><
先にちゃんと調べてから作らないと無駄ですね−。

2009年5月27日水曜日

Mac版ATOK + HHK Professional2で日本語入力をするときの操作方法まとめ

1年ほど愛用していたCENTURY社のWhite Knight (http://www.century.co.jp/products/ck-112cmw-psu.html) の調子が悪くなってしまい、ついにはキーを認識しなくなってしまったため、現在家のiMacにはHHK Professional2 黒 (http://www.pfu.fujitsu.com/hhkeyboard/hhkbpro2/) をつなげています。

ASCII配列自体にはある程度慣れてきたのですが、問題が日本語入力です。家のiMacではATOK for Mac 2008をMS-IME風スタイルで利用していますが、ATOKだろうがMS-IMEだろうがことえりだろうが、一般的には皆さん変換時に文節を矢印キーで動かしたり伸縮させたりしていることと思います。ところが、HHK Professional2にはまともな矢印キーがありません(あるにはありますが、これは矢印キーとして見なしてはいけないと思います・・・)。これでは日本語入力が出来ない!ということで、HHKらしくControlキーとアルファベットの組み合わせて日本語入力を華麗にこなすための操作方法をまとめてみました。

2009/06/01追記:以下のショートカットキーはMS-IME設定のときのものです。WindowsのMS-IME上でも全く同様に使えました!


■次の文節へ・前の文節へ
左手で行います。
Control + Dで次の文節へ
Control + Sで前の文節へ
Control + Aで先頭の文節へ
Control + Fで末尾の文節へ


■文節区切りの伸縮
右手で行います。
Control + Kで文節を短くする
Control + Lで文節を長くする


■文字の削除
変換中にControl + Hで単語削除
変換中にControl + Gで一文字削除


■ひらがな・カタカナ・英字などに一発変換
主に右手で行います。
Control + Uでひらがな
Control + Iでカタカナ
Control + Oで半角カタカナ
Control + Pで全角英字
Control + Tで半角英字


■これだけ覚えておけば大丈夫!
でもどうしても気に入らない場合は、「カスタマイザ」というとっても便利な機能があるので、すべて自分の好きなように変えてしまいましょう。


■しかし驚いたのは
ほとんどこれらの機能を使うことなくATOKが一発変換してくれたこと。実際、このBlogを書いている最中にも、Controlキーには2回程度しか触れていません。ATOKはマジに神。

2009年5月19日火曜日

UINavigationの片方のサイドに複数個のボタンを持たせたい



こんな感じでUINavigationBarの片方のサイドに複数個のボタンをおく方法を探してみました。UIToolBarと異なり、UINavigationBarではUINavigationItem.rightBarButtonItem, UINavigationItem.leftBarButtonItem, それからタイトル部分と、最大でも3個しかアイテムを配置することが出来ません。そのため、複数のボタンを一つの配置箇所にまとめて配置したい場合には、カスタムビューを作成する必要があります。
Appleの配布しているデモアプリケーションにもありますが、こういう場合にはUISegmentedControlのmomentaryプロパティをYESに指定して、ボタンみたいに利用するのがいちばん良いようです。
NSArray *items = [NSArray arrayWithObjects:@"Add", @"YourTurn", nil];
UISegmentedControl *segmentedControl = [[[UISegmentedControl alloc] initWithItems:items] autorelease];
segmentedControl.selectedSegmentIndex = UISegmentedControlNoSegment;
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.momentary = YES;
[segmentedControl addTarget:self action:@selector(segmentedControlClicked:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:segmentedControl] autorelease];

この方法でひとまずはUINavigationItemの右側に複数のボタンを作ることが出来ました。しかしまだまだ完璧ではなくて、現地点で分かっている限りでも二つの問題があります。
  1. ボタンの背景色が通常のボタンとは異なる色になる
  2. 個々のボタンひとつひとつを個別にDisableすることができない

Appigo Todoなんかは普通にUINavigationBarの右側に3つもまとめてボタンを綺麗に配置してあったりするのですが、一体全体どうやってるんでしょうね?・・・うーん。