Block在内存中的三种位置
首先,在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy,但是在ARC的中,一般都是分配在堆中。
@interface SecondViewController ()
typedef void (^blk)(void);
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__block int val = 1;
//block保存在堆区
__strong blk heapBlock = ^{
val = 2;
};
NSLog(@"heapBlock: %@", heapBlock);
//block保存在栈区
__weak blk stackBlock = ^{
val = 2;
};
NSLog(@"stackBlock: %@", stackBlock);
//block保存在全局区
blk globalBlock = ^{
};
NSLog(@"globalBlock: %@", globalBlock);
}
Log输出
2018-04-09 15:50:14.351940+0800 TestMRC[29483:4870456] heapBlock: <__NSMallocBlock__: 0x60000024c090>
2018-04-09 15:50:14.352184+0800 TestMRC[29483:4870456] stackBlock: <__NSStackBlock__: 0x7ffee57543d0>
2018-04-09 15:50:14.352318+0800 TestMRC[29483:4870456] globalBlock: <__NSGlobalBlock__: 0x10a4a9148>
简单来说分两种情况
A、没有引用外部变量 --- block存放在全局区
B、引用了外部变量----显式声明为weak类型的block则存放在栈区,反之则是存在在堆区的,也就是说block是strong类型的。
内存分区
iOS App的内存分配大概如下所示

从下到上,是低地址到高地址。
代码区(Code)
: 存放App代码;常量区(Const)
:存放App声明的常量;全局区/静态区(Global/Static)
:存放App的全局变量与静态变量;堆区(heap)
:一般是存放对象类型,需要手动管理内存,ARC项目的话,编译器接手内存管理工作;堆区是使用链表来存储的空闲内存地址的,是不连续的,而链表的遍历方向是由低地址向高地址。栈区(stack)
:存放的局部变量、一旦出了作用域就会被销毁,不需要手动管理,栈区地址从高到低分配,遵循先进后出的,如果申请的栈区内存大小超过剩余的大小,就会反正栈区溢出overflow问题,一般栈区内存大小为2M。
使用block的注意事项
因为在ARC中,block还是有可能为NSStackBlock类型的,也就是说,随着作用域结束,block将会销毁回收。
所以有时候需要对block进行copy操作,手动复制到堆区内存中,防止被回收,导致block无法使用。
自动copy
以下情况,系统会将block自动复制到堆上,自动对block调用copy方法。
1、当 block 作为函数返回值返回时;
2、当 block 被赋值给__strong修饰的 id 类型的对象或 block 对象时;
3、当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。
因为在ARC下,对象默认是用__strong修饰的,所以大部分情况下编译器都会将 block从栈自动复制到堆上。
不自动copy
以下情况,系统不会自动复制到堆上,也就是说,作用域结束,block就被回收销毁。
block 作为方法或函数的参数传递。
block 作为临时变量,没有赋值给其他block
注意,上述不一定是从栈区复制到堆区,也有可能是global 区复制过去的。
__block的探究
ARC项目中,使用Block一般都是NSMallocBlock类型的Block,也就是说Block会存放在堆区内存中,当Block捕获外部变量的时候,如果外部变量是存在栈区内存中,Block只是对其值进行复制操作,所以Block只能读取栈区的内存。简单来说,因为Block其实也可以看做一个函数,是有自己的作用域的,与栈区变量的作用域并不同,所以无发修改栈区的值。
但是对栈区的变量加了前缀__block
后,Block就会创建一个带isa指针的构体,里面存放着该变量的内存地址等信息。因为平常使用的Block一般都是存放在堆区的,而且Block里面的变量也是存在堆区,所以,这个原本存放在栈区的变量加了__block
后,就变成了一个存放在堆区的Block变量(结构体),这样一来,在Block里面当然就可以修改它的值了。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
__block int a = 1;
NSLog(@"a value = %d, addr = %p",a,&a);
void (^blk)(void) = ^{
a = 2;
NSLog(@"a value = %d, addr = %p",a,&a);
};
blk();
NSLog(@"a value = %d, addr = %p",a,&a);
}
打印的结果如下,地址更改了。
2018-04-10 17:32:02.312106+0800 TestMRC[68224:5848911] a value = 1, addr = 0x7ffeef36a450
2018-04-10 17:32:02.312307+0800 TestMRC[68224:5848911] a value = 2, addr = 0x604000235098
2018-04-10 17:32:02.312410+0800 TestMRC[68224:5848911] a value = 2, addr = 0x604000235098
这样就可以解释,为什么全局变量和属性可以在Block直接修改其值了,因为这些变量的生命周期是随着类的结束而结束的。
网友评论