NSTableViewのセルについてかなり悩まされたのでメモしておく。
まず、Cocoa Touchと同様に
- NSTableViewDataSource
- NSTableViewDelegate
の2つのプロトコルがある。
Cocoa TouchではreuseIdentifierを使ってcellのインスタンスを再利用するようになっているが、CocoaではNSTableColumnにsetDataCell:したcellを再利用するようになる。
#pragma mark NSTableViewDataDelegate
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return [tableColumn dataCell];
}
注意したいのが、NSTableViewの各カラムで使用されるセルは再利用されるということ。つまり同じインスタンスを使い回している。
NSTextFieldCellやNSImageCellなどのtextやimageは列と行に対応したobjectValueをセットしてやる。
イメージやテキストを含んだcell(NSCell)はInterfaceBuilderで作れない。
NSCellを継承したCustomCellクラスを作りコードでtextやimageを描画してやる必要がある。また、cellはobjectValueを1つしか受け取れない。
- (id)tableView:objectValueForTableColumn:row:
id型で返すことになるのでAlbumオブジェクトのようなtitle, song等を含むオブジェクトを返してやる。
あとはCustomCellクラスでAlbunオブジェクトの情報を描画してやればいい。
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
こんな感じ。
Macを使っていると知らず知らずのうちに同じネットワーク内のPCやサーバーを見つけてくれる。これすごく不思議。
Bonjourっていう技術が使われているんだけど、これについてはRefernceでが2冊、サンプルコードが1つ公開されてる。
ReferenceのほうはBonjourの仕組みとNSNetServiceクラスとNSNetServiceBrowserクラスについて解説してる。これはがんばって読んでおかないといけない内容だろうね。
サンプルはクライアント/サーバー型で画像ファイルが送受信できる内容になっていてこれをベースにすればなんでも出来そうな予感。他にもパスコードやら暗号化やら追加する必要があるだろうけど。
あ〜、ちょっとだけでも関わっておけば良かった、と後悔してるけど今となっては仕方ない。
NSApplicationDelegate Protocol の applicationWillTerminate: についてメモ。
applicationWillTerminate: は、アプリケーションが終了する直前に呼ばれる。ドキュメントによる最後のクリーンアップ処理を行うことを想定している。
applicationWillTerminate: は、applicationShouldTerminate: が NSTerminateNow を返したときに通知される。
そして、ウィンドウがひとつしかない場合や最後のウィンドウが閉じた場合には applicationShouldTerminateAfterLastWindowClosed: が呼ばれ、YESを返した場合には上記の applicationShouldTerminate: が呼ばれる。
まとめてみる。
- (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication *)theApplication {
// 最後のウィンドウが閉じられた
// 問題があればNOを返す。問題がなければYESを返す。
return YES; // applicationShouldTerminate: が呼ばれる
}
- (NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *)sender
{
// NSAppが終了しようとしている。
// 問題があれば NSTerminateCancel を返す。
// 問題がなければ NSTerminateNow を返す。
return NSTerminateNow; // applicationWillTerminate: が呼ばれる
}
- (void) applicationWillTerminate: (NSNotification *)aNotification
{
// 最後のクリーンアップ処理を行う。
}
NSApplication の terminate: は、メニューの [アプリケーションを終了 ⌘Q] を実行すると勝手に呼ばれる。
ちなみに、terminate: は applicationShouldTerminate: を実行する。
普通にアプリケーションを終了する場合には、terminate: は実行しなくていい。
そんな感じ。
NSThread の detacheNewThreadSelector:toTarget:withObject: についてメモ。
例えば次のような意味の無いコードを実行すると、NSAutoreleaseNoPool のエラーが発生する。
- (void) hogeThread {
NSMutableArray *anArray = [NSMutableArray array];
int i = 0;
while (isInThread) {
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[anArray addObject: [NSString stringWithFormat:@"%d". i++]];
}
[NSThread exit];
}
- (void) stopThread {
isInThread = NO;
}
- (void) doThread {
isInThread = YES;
[NSThread detachNewThreadSelector:@selector(hogeThread)
toTarget:self
withObject:nil];
}
まず、NSMutableArray はクラスメソッド array によって autorelease 済みの状態で用意される。
また、NSStringも同様にクラスメソッド stringWithFormat: によって autorelease 済みで用意される。
通常、アプリケーションはメインスレッド用にAutoreleasePoolをひとつ持っている。
新しいスレッドを用意した場合、別途AutoreleasePoolを用意してやらないと、NSMutableArray とNSString でメモリリークが発生する。
- (void) hogeThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableArray *anArray = [NSMutableArray array];
int i = 0;
while (isInThread) {
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[anArray addObject: [NSString stringWithFormat:@"%d". i++]];
}
[NSThread exit];
[pool release];
}
これでOK。
Objective-C のコードを書いていると、理解しているはずなのに、ときどきわからなくなる。
そう、retain、release、autorelease の3兄弟だ。
この例はとってもシンプル。何をしたいのか理解できないが。
- (void) hoge: (id)obj {
UIView *aView = [[UIView alloc] initWithFrame:CGRectZero];
[myView addSubView:aView];
[self hoge:obj];
}
どうやら UIView のインスタンスを作って、myView に追加したいらしい。
だけど、このコードだと、aViewがメモリリークする。
2行目で、aViewのretainCountが1になる。
3行目で、myViewがaViewを使うことになるので、aViewのretainCountが2になる。
そして、このスコープを抜ける・・・aViewのretainCountは2のまま。
myView が release されると aView の retainCount は1になる。ゼロにならない。
だからこのように書くのがいい。
- (void) hoge: (id)obj {
UIView *aView = [[UIView alloc] initWithFrame:CGRectZero];
[myView addSubView:aView];
[aView release]; // この後、aViewは使わないからreleaseする
[self hoge:obj];
}
もしくは aView 生成時に autorelease しておく。
- (void) hoge: (id)obj {
UIView *aView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
[myView addSubView:aView];
[self hoge:obj];
// aView は relase 予約済みなので気にしなくていい。
}
あ、retain について触れてなかった。