Memory mapped files(mmap)をソースとして作成した NSData を不要になったタイミング(dealloc)で開放したい。安易に考えると NSData をサブクラスして dealloc をオーバライドする方法が思いつくが、NSData はクラスクラスタなので実際には難しい。そこで後処理をBlocksとして objc_setAssociatedObject で NSDataに登録しておくというテクニックが紹介されている。NSDataのインスタンスが開放されるタイミングで Associated Object も開放されることを利用している。
以下、ソースコード引用。まず開放処理のBlocksを格納するクラスを1つ用意する。
@interface DeallocHandler : NSObject @property (readwrite, copy) void (^theBlock)(void); @end @implementation DeallocHandler @synthesize theBlock; - (void)dealloc { if (theBlock != nil) { theBlock(); } } @endこのインスタンスが開放されるタイミング(dealloc)でプロパティに設定した Blocksを呼び出すようにしておく。
次にこの DeallocHandler を Associated Object として登録するカテゴリの定義。
static char *deallocArrayKey = "deallocArrayKey"; @implementation NSObject (deallocBlock) - (void)addDeallocBlock:(void (^)(void))theBlock; { NSMutableArray *deallocBlocks = objc_getAssociatedObject(self, &deallocArrayKey); if (deallocBlocks == nil) { deallocBlocks = [NSMutableArray array]; objc_setAssociatedObject(self, &deallocArrayKey, deallocBlocks, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } DeallocHandler *handler = [[DeallocHandler alloc] init]; [handler setTheBlock:theBlock]; [deallocBlocks addObject:handler]; } @end
次のように使う。
NSData *mappedData = [NSData dataWithBytesNoCopy:mappedFile length:[fileSize intValue] freeWhenDone:NO];として mmapで開いた Memory Mapped なファイル mappedFile を元に NSData を作成する。
そして NSData が開放される時にこの mappedFile を開放する処理を先のカテゴリを使って登録しておく。
[mappedData addDeallocBlock:^{ munmap(mappedFile, [fileSize intValue]); }];
こうすることで NSData 開放時に後始末の処理を流すことができる。
- - - -
Associated Object に Blocks を登録するというのはちょっと面白いテクニック(トリック?)で dealloc 以外にも何か応用が効きそう。
なおこの記事(とそのコメント)ではもう一つ議論になっていることがある。それは NSData でファイルを読み込む時のオプション NSDataReadingMappedAlways がうまく働かないことがあるという点。Mac OS X では良さそうだが iOS ではダメなのか?この辺りは使った経験が無いのでいまいちわからない。どなたか情報があれば教えて欲しい(コメントへどうぞ)。