ARC下声明的block在堆区、栈区、全局区?

分两种情况

  • Block不引用外部变量

typedef void(^SDBlock)(void);

@interface ViewController ()
//@property (nonatomic, strong) NSDictionary *dict;
@property (nonatomic, copy) NSMutableString *mStr;
@property (nonatomic, strong) SDBlock block;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 全局区
    static int b = 20;
    // 栈区
    int a = 10;
    typeof(self) __weak weakSelf = self;
    
    // block作为属性
    self.block = ^{
//        weakSelf.array;
    };
    NSLog(@"\n 栈: %p", &a);
    NSLog(@"\n 全局: %p", &b);
    NSLog(@"\n block: %p", self.block);
    
    // block作为参数
    [Person testBlockSectionCallback:^(NSString * _Nonnull str) {
        
    }];
/* Person内部实现
   + (void)testBlockSectionCallback:(void(^)(NSString *str))callback {
    if (callback) {
        NSLog(@"\nmethod block: %@", callback);
    }
}
**/
}

输出结果

// 内存地址
2019-06-25 14:41:17.405952+0800 Test[12104:1070611] 
 栈: 0x16d6651b4
2019-06-25 14:41:17.405991+0800 Test[12104:1070611] 
 全局: 0x10283ffe8
2019-06-25 14:41:17.406005+0800 Test[12104:1070611] 
 block: 0x102820a98
2019-06-25 14:41:17.406047+0800 Test[12104:1070611] 
method block: 0x102820ad8

// *****打印block类型查看******
2019-06-25 14:43:29.251564+0800 Test[12116:1071345] 
 栈: 0x16d9091b4
2019-06-25 14:43:29.251599+0800 Test[12116:1071345] 
 全局: 0x10259bfe8
2019-06-25 14:43:29.251639+0800 Test[12116:1071345] 
 block: <__NSGlobalBlock__: 0x10257ca98>
2019-06-25 14:43:29.251691+0800 Test[12116:1071345] 
method block: <__NSGlobalBlock__: 0x10257cad8>

在不引用外部变量时,无论block作为属性还是作为方法中的参数,它都在内存中的全局区。

  • 引用外部变量
    // 全局区
    static int b = 20;
    // 栈区
    int a = 10;
    typeof(self) __weak weakSelf = self;
    
    // block作为属性
    self.block = ^{
        NSLog(@"%@", weakSelf.array);
    };
    NSLog(@"\n 栈: %p", &a);
    NSLog(@"\n 全局: %p", &b);
    NSLog(@"\n block: %@", self.block);
    
    // block作为参数
    [Person testBlockSectionCallback:^(NSString * _Nonnull str) {
        NSLog(@"%@", weakSelf.array);
    }];

// 输出结果

2019-06-25 14:49:24.994145+0800 Test[12152:1073586] 
 栈: 0x16d31119c
2019-06-25 14:49:24.994192+0800 Test[12152:1073586] 
 全局: 0x102b93fd8
2019-06-25 14:49:24.994253+0800 Test[12152:1073586] 
 block: <__NSMallocBlock__: 0x282a15110>
2019-06-25 14:49:24.994337+0800 Test[12152:1073586] 
method block: <__NSStackBlock__: 0x16d311130>

从表象看,作为属性的block在堆区内存,而方法中的block引用了外部变量后,内存在栈区。

带着上述表象问题,来看下GCD中的block在内存中的哪个区?

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD中使用的是dispatch_block_t,为了方便获取block内存区段,我仿照GCD将dispatch_block_t作为方法的参数。

- (void)go:(dispatch_block_t)block {
    NSLog(@"\n gcd: %@", block);
}

在未引用尾部变量时,dispatch_block_t位于全局区,在引用外部变量后,dispatch_block_t和上面Person方法中的block一致,表现的也是在栈区。

但是,这和栈内存的内存管理是不相符的,设置在栈上的block,如果其作用域结束,该block就被销毁,但是此处的block内存明显是没有被回收的。

在查看Apple文档的时候,它的解释如下:


 * @typedef dispatch_block_t
 *
 * @abstract
 * The type of blocks submitted to dispatch queues, which take no arguments
 * and have no return value.
 *
 * @discussion
 * When not building with Objective-C ARC, a block object allocated on or
 * copied to the heap must be released with a -[release] message or the
 * Block_release() function.
 *
 * The declaration of a block literal allocates storage on the stack.
 * Therefore, this is an invalid construct:
 * 
 * dispatch_block_t block;
 * if (x) {
 *     block = ^{ printf("true\n"); };
 * } else {
 *     block = ^{ printf("false\n"); };
 * }
 * block(); // unsafe!!!
 * 
 *
 * What is happening behind the scenes:
 * 
 * if (x) {
 *     struct Block __tmp_1 = ...; // setup details
 *     block = &__tmp_1;
 * } else {
 *     struct Block __tmp_2 = ...; // setup details
 *     block = &__tmp_2;
 * }
 * 
 *
 * As the example demonstrates, the address of a stack variable is escaping the
 * scope in which it is allocated. That is a classic C bug.
 *
 * Instead, the block literal must be copied to the heap with the Block_copy()
 * function or by sending it a -[copy] message.

结论是,其实这个dispatch_block_t并不在栈上,而是在其他内存区域。
我没懂这个解释,我也没有验证出来苹果上面说的 "这是个经典的 C bug"。
在这里有人和我遇到相同的疑惑。

在ARC模式下,blockcopy操作是发生在以下时机:

  • 主动调用[Block copy]方法
  • Block作为函数返回值
  • 将Block赋值给__strong修饰符id类型的类或Block类型的成员变量时。
  • 方法名中含有usingBlock的Cocoa框架方法或GCD的API传递block时。

了解了copy操作发生的时机,就能够解释为什么会有上面的现象了,作为引用外部变量属性的block,从栈区copy到堆区发生在初始化的时候,也就是

// 这个操作会触发[block copy]
self.block = ^{
    NSLog(@"%@", weakSelf.array);
};

在方法中的block的copy,发生在方法调用时,也就是

if (block) {
    block();
}

所以,第一次获取的栈地址是原始的未发生copy操作之前的内存。

结论:
未引用外部变量时

block无论作为属性还是参数,都在全局区。

引用外部变量时

block声明在栈区,但在block调用、赋值给其它strong修饰的变量时,会发生copy操作,从栈区copy到堆区,在GCD及其它方法中的block都是在调用时发生copy操作。

参考

天下文章一大抄,也不知道下面的文章是谁抄谁的,我没有找到源文档,只好引用你们吧
Block被copy堆上的时机
Block copy时机

你可能感兴趣的:(ARC下声明的block在堆区、栈区、全局区?)