ARCはNSStackBlockを降りてどこに行ったの?

2072 ワード

私はObjective-CのBlockのタイプについて、MRCの下にNSGlobalBlock、NSMallocBlock、NSStackBlockの3種類のblockがありますが、ARCの下にはNSStackBlockのタイプはありません.
RACでは、次のテストを行います.
    CGFloat f = 1.1;
    
    NSLog(@"%@", ^{NSLog(@"%lf",f);});
    
    NSLog(@"%@",[^{NSLog(@"%lf",f);} copy]);
    
    void(^deliveryBlock)(void) = ^{NSLog(@"%lf",f);};
    NSLog(@"%@", deliveryBlock);

出力ログは
2017-03-24 22:20:22.526 testdemo[48961:588668] <__nsstackblock__:>
2017-03-24 22:20:22.526 testdemo[48961:588668] <__nsmallocblock__:>
2017-03-24 22:20:22.527 testdemo[48961:588668] <__nsmallocblock__:>

blockを直接印刷したとき、彼のタイプはNSStackBlockだったことに気づきました.このblockを割り当てた後、deliveryBlockを印刷した結果、NSMallocBlockタイプになりました.すなわち、NSStackBlockタイプはARCの下に存在するが、コンパイラはスタック領域のblockをスタック領域にコピーした(付与とcopyはここで効果は同じ).
原因について
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSObject *obj = [[NSObject alloc]init];
    void(^deliveryBlock)(void) = ^{
        NSLog(@"%@",obj);
    };
    [self didBlock:deliveryBlock];
}


- (void)didBlock:(void(^)(void)) block {
    NSLog(@"%@",block);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}

上のコードを参照して、deliveryBlockがNSStackBlockタイプである場合、deliveryBlockをメソッドパラメータとして別のメソッドに渡すと、deliveryBlockが元のメソッドの呼び出しスタックに存在せず、新しいメソッドがdeliveryBlockを呼び出すタイミングが分からず、新しいメソッドが呼び出されたときにdeliveryBlockがスタック領域のobjが解放される可能性がある.すなわち、ARC下コンパイラは、NSStackBlockタイプのblock転送中に自動的に最適化される.
さらに検証するために、以下のテストを行いました.
- (void)viewDidLoad {
    [super viewDidLoad];
    [self test:^{
       NSLog(@"%@",self.object);
    }];
}
- (void)test:(void(^)(void))block {
    NSLog(@"%@",block); //log: <__nsstackblock__:>
}

objectがselfのプロパティである場合、objectはvc全体のライフサイクルに存在するため、呼び出しスタックから離れる問題はないため、コンパイラはスタックに自動的にコピーしません.