2012年2月23日木曜日

NSData が dealloc する時に後始末を行いたい



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


0 件のコメント:

コメントを投稿