チャンク単位でNSData
を作成して処理させたら、autoreleasepoolにオブジェクトが溜まって大変だったのでメモ。
- (void)func
{
for (int i = 0; i < 1000; ++i)
{
char text[] = "sample text";
NSData * data = [[NSData alloc] initWithBytes:text length:sizeof(text)];
[self operate:data];
}
}
- (void)operate:(NSData *)data
{
const char * chars = (const char *)data.bytes;
NSLog(@"%02d %02d %02d %02d", chars[0], chars[1], chars[2], chars[3]);
}
こんな感じでNSDataを別の関数に渡して処理させていたら、メモリを圧迫してしまいました。
原因
結局 data.bytes
を呼び出すとautoreleasingの参照が増えて、ループが終わってRunLoopに戻るまでオブジェクトが解放されませんでした。
operate:
を@autoreleasepool{}
で囲うことで、メモリの問題は解消されました。
bytes
プロパティはconst void*
なので油断していたら、ここでも参照管理は(ある程度)されていました。
InstrumentsでAllocationsの様子を見ていると、_NSInlineData
の# Persistentが増え続けて、ループが終わるとごっそり# Transientに移るのが確認できました(ループが終わると消えるのでリークにはならない)。
@autoreleasepool
に入れると# Persistentはほぼ変わらず# Transientのみ増えるのでautoreleaseで解放されているようです。
(_objc_autoreleasePoolPrint()
は試したけど何も出力してくれませんでした)
もう少し詳しく
bytes
プロパティは @property (readonly) const void *bytes NS_RETURNS_INNER_POINTER;
と宣言されていて、NS_RETURNS_INNER_POINTER
が記されています。
LLVMのARC資料の7.7 Interior pointersによると、
the object’s lifetime will be extended until at least the earliest of:
- the last use of the returned pointer, or any pointer derived from it, in the calling function or
- the autorelease pool is restored to a previous state.
となっています。
これに従うとchars
のスコープが終わった時点で前者の条件を満たして参照が解放されると思うのですが、実際には、理由は分かりませんが(void *
だから?)後者のautoreleaseの処理まで残っていたようです。