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 ではダメなのか?この辺りは使った経験が無いのでいまいちわからない。どなたか情報があれば教えて欲しい(コメントへどうぞ)。


