分两种情况
- 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模式下,block
的copy
操作是发生在以下时机:
- 主动调用[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时机