seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

[objc] Objective-Cのブロックと、ブロックにキャプチャされたオブジェクトの寿命の確認

上記Objective-Cのコードでは、ループ中にブロックを作成して配列に格納しているが、

  1. ここで生成されるブロックは本当にループ回数分だけ独自のものが生成されているのか、
  2. ブロックにキャプチャされたローカルスコープのオブジェクトは、ブロックが残存するかぎりちゃんと保持されるのか、

ということを確認したくなった。


※ Blockの使い方についてネットで調べていたら、ループ中に生成したBlockが同じアドレスになっており、スタック上の変数(ローカルスコープの変数のこと?)の参照を残すとクラッシュするからコピーすべし、という記事をみかけたので。(ARC以前のもの)
http://d.hatena.ne.jp/terazzo/20101103/1288810554


以下は、ARC(automatic reference counting)をサポートするMac OS X 10.7 Lion上のXcode4.3.2で試したものである。

#import <Foundation/Foundation.h>

// ブロックにキャプチャされるオブジェクトの追跡実験用
@interface ChkObj : NSObject
- (void)showSelf;
- (void)dealloc;
@end

// ブロックにキャプチャされるオブジェクトの追跡実験用(実装)
@implementation ChkObj
- (void)showSelf
{
    NSLog(@"chkobj: %@", self);
}
- (void)dealloc
{
    // オブジェクトが破棄されたときに診断メッセージを出す
    NSLog(@"dealloc: %@", self);
}
@end

// ブロック型の定義 (関数ポインタに相当)
typedef int (^func_t)(int);
typedef void (^action_t)(int);

// ブロックの配列を生成して返す
NSArray *makeFuncs()
{
    id arr = [NSMutableArray array];
    for (int idx = 0; idx < 10; idx++) {
        @autoreleasepool {
            
            // 追跡用オブジェクトを生成し診断メッセージを表示
            id obj = [[ChkObj alloc] init];
            [obj showSelf];
            
            func_t func = ^(int offset){
                // ブロックが作られた時点の自動変数がキャプチャされる.
                // このブロックを実行した場合、作られた時点の変数が見れるということ.
                
                // ブロックにキャプチャされている追跡オブジェクトの診断メッセージ表示する.
                // なお、この行を消すとobjはキャプチャ不要になるので
                // ループが回るごとに一個づつ破棄される.
                [obj showSelf];
                
                return offset + idx * 2;
            };
            NSLog(@"block=%@", func);
            [arr addObject: func];
        }
    }
    return arr;
}

void callFuncs(int offset, NSArray *arr, action_t action)
{
    // 高速列挙(NSFastEnumerationを実装しているものはforeachできる)
    for (func_t func in arr) {
        NSLog(@"block=%@", func);
        action(func(100));
    }
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {

        id arr = makeFuncs();
        
        NSLog(@"!!!step2!!!");

        // ブロックの中から書き換える場合は __block属性をつける
        int __block count = 0;
        
        id buf = [NSMutableString string];
        callFuncs(100, arr, ^(int ret) {
            count++;
            // 自動変数がキャプチャされているので
            // bufに対する参照(ポインタのコピー)は、そのまま使える.
            [buf appendString: [NSString stringWithFormat: @"%d\n", ret]];
        });

        printf("count=%d\n%s", count, [buf UTF8String]);

        // チェック用オブジェクトをキャプチャしているブロックが、
        // このautoreleasepoolのスコープの終了により解放されることで
        // チェック用オブジェクトも、ここで解放される.
        NSLog(@"!!!step3!!!");
    }
    return 0;
}

実行結果は以下のとおり。

  1. ループごとにブロックは生成されており、
  2. キャプチャされた追跡オブジェクトは、ブロックが解放されるときまで解放されていない

ということが確認できる。


よって、ブロックにキャプチャされているオブジェクトは、
ブロックの知らないうちに解放されり危険なポインタになったりする心配はいらなそうである。
(ARCによるところが大きいのかもしれない。)

2012-06-09 23:23:11.533 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3021d0>
2012-06-09 23:23:11.535 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892d01a50>
2012-06-09 23:23:11.535 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892d01c70>
2012-06-09 23:23:11.536 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f314970>
2012-06-09 23:23:11.536 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900150>
2012-06-09 23:23:11.537 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892d01da0>
2012-06-09 23:23:11.537 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892e00330>
2012-06-09 23:23:11.537 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f315e40>
2012-06-09 23:23:11.538 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900000>
2012-06-09 23:23:11.538 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f3017e0>
2012-06-09 23:23:11.538 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3100c0>
2012-06-09 23:23:11.539 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9894900020>
2012-06-09 23:23:11.539 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900010>
2012-06-09 23:23:11.540 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9894900110>
2012-06-09 23:23:11.540 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3028d0>
2012-06-09 23:23:11.540 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e00340>
2012-06-09 23:23:11.541 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892d01470>
2012-06-09 23:23:11.541 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e00370>
2012-06-09 23:23:11.541 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900140>
2012-06-09 23:23:11.542 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e002e0>
2012-06-09 23:23:11.542 BlocksTest[3840:403] !!!step2!!!
2012-06-09 23:23:11.543 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892d01a50>
2012-06-09 23:23:11.543 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3021d0>
2012-06-09 23:23:11.543 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f314970>
2012-06-09 23:23:11.544 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892d01c70>
2012-06-09 23:23:11.544 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892d01da0>
2012-06-09 23:23:11.544 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900150>
2012-06-09 23:23:11.545 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f315e40>
2012-06-09 23:23:11.545 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892e00330>
2012-06-09 23:23:11.545 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x10f3017e0>
2012-06-09 23:23:11.546 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900000>
2012-06-09 23:23:11.550 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9894900020>
2012-06-09 23:23:11.550 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3100c0>
2012-06-09 23:23:11.551 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9894900110>
2012-06-09 23:23:11.551 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900010>
2012-06-09 23:23:11.551 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e00340>
2012-06-09 23:23:11.552 BlocksTest[3840:403] chkobj: <ChkObj: 0x10f3028d0>
2012-06-09 23:23:11.552 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e00370>
2012-06-09 23:23:11.553 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9892d01470>
2012-06-09 23:23:11.553 BlocksTest[3840:403] block=<__NSMallocBlock__: 0x7f9892e002e0>
2012-06-09 23:23:11.553 BlocksTest[3840:403] chkobj: <ChkObj: 0x7f9894900140>
count=10
100
102
104
106
108
110
112
114
116
118
2012-06-09 23:23:11.554 BlocksTest[3840:403] !!!step3!!!
2012-06-09 23:23:11.554 BlocksTest[3840:403] dealloc: <ChkObj: 0x10f3021d0>
2012-06-09 23:23:11.555 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9892d01c70>
2012-06-09 23:23:11.555 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9894900150>
2012-06-09 23:23:11.555 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9892e00330>
2012-06-09 23:23:11.556 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9894900000>
2012-06-09 23:23:11.556 BlocksTest[3840:403] dealloc: <ChkObj: 0x10f3100c0>
2012-06-09 23:23:11.556 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9894900010>
2012-06-09 23:23:11.557 BlocksTest[3840:403] dealloc: <ChkObj: 0x10f3028d0>
2012-06-09 23:23:11.557 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9892d01470>
2012-06-09 23:23:11.558 BlocksTest[3840:403] dealloc: <ChkObj: 0x7f9894900140>

コード中のコメントにあるように、ブロックの中で追跡オブジェクトを参照しない場合、
追跡オブジェクトはキャプチャされないのでブロックの寿命とは無関係になる。
その場合、ループごとに生成されて、そのループの終了ごとに(ARCにより)破棄されることになるはずである。

2012-06-09 23:26:31.195 BlocksTest[3863:403] chkobj: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.197 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x105914970>
2012-06-09 23:26:31.197 BlocksTest[3863:403] dealloc: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.198 BlocksTest[3863:403] chkobj: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.198 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd100120>
2012-06-09 23:26:31.198 BlocksTest[3863:403] dealloc: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.199 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.199 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x1059168c0>
2012-06-09 23:26:31.199 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.200 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.200 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x105916990>
2012-06-09 23:26:31.200 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.201 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.201 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x1059166d0>
2012-06-09 23:26:31.202 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.219 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.219 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd0012f0>
2012-06-09 23:26:31.220 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd000f50>
2012-06-09 23:26:31.220 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebb500280>
2012-06-09 23:26:31.220 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd100170>
2012-06-09 23:26:31.221 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebb500280>
2012-06-09 23:26:31.221 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd100000>
2012-06-09 23:26:31.221 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd1001f0>
2012-06-09 23:26:31.222 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd100000>
2012-06-09 23:26:31.222 BlocksTest[3863:403] chkobj: <ChkObj: 0x7feebd100000>
2012-06-09 23:26:31.223 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebb500490>
2012-06-09 23:26:31.223 BlocksTest[3863:403] dealloc: <ChkObj: 0x7feebd100000>
2012-06-09 23:26:31.223 BlocksTest[3863:403] chkobj: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.244 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebb500280>
2012-06-09 23:26:31.245 BlocksTest[3863:403] dealloc: <ChkObj: 0x1059146c0>
2012-06-09 23:26:31.245 BlocksTest[3863:403] !!!step2!!!
2012-06-09 23:26:31.246 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x105914970>
2012-06-09 23:26:31.246 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd100120>
2012-06-09 23:26:31.246 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x1059168c0>
2012-06-09 23:26:31.247 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x105916990>
2012-06-09 23:26:31.247 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x1059166d0>
2012-06-09 23:26:31.247 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd0012f0>
2012-06-09 23:26:31.248 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd100170>
2012-06-09 23:26:31.248 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebd1001f0>
2012-06-09 23:26:31.249 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebb500490>
2012-06-09 23:26:31.249 BlocksTest[3863:403] block=<__NSMallocBlock__: 0x7feebb500280>
count=10
100
102
104
106
108
110
112
114
116
118
2012-06-09 23:26:31.249 BlocksTest[3863:403] !!!step3!!!

こちらも想定どおり、プロックとは無関係にただちに破棄されていることが確認できる。

結論

ARCを使っているかぎりでは、

  • ブロックはブロックの定義部の実行ごとに都度作成され、
  • そこで参照している外側の変数がキャプチャされた場合には、
    • その変数はブロックに明示的に参照されている状態となり、
    • ブロックが解放されるまでは解放されない、

という、他言語のクロージャと同等の動きとなることを期待して良いようである。


もし、ARCを使わない場合には、terazzoさんの指摘のように注意が必要なのかもしれない。
この点は気をつけてみたいと思う。