内存五大内存分区-----堆、栈、自由存储区、全局/静态存储区和常量存储区.
1. 堆,就是那些由new分配的内存块,它们的释放编译器不管,由我们的应用程序控制
2. 栈,就是那些由编译器在需要的时候分配,不需要的时候自动清除的变量的存储区。里面的变量通常是局部变
量、函数参数等。
3. 全局区(静态区) (static) 全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区
域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后有系统释放。
4. 文字常量区 存放常量字符串,程序结束后由系统释放
5. 程序代码区 存放函数的二进制代码
Block 有三种类型,分别是:
__NSConcreteStackBlock ————————栈中
__NSConcreteGlobalBlock ————————数据区域中
__NSConcreteMallocBlock ————————堆中
ARC下NSStackBlock已经基本不会出现的了, 除非可以强行让它出现,但实际开发中应该不会做这么无聊的事。
先看看一个DEMO。
- (void)viewDidLoad {
[superviewDidLoad];
void(^blockA)() = ^{
NSLog(@"just a block");
};
NSLog(@"%@", blockA);
int value = 10;
void(^blockB)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockB);
void(^ __weak blockC)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockC);
}
三个NSLog打印的内容为:
<__NSGlobalBlock__: 0x105a730d0>
<__NSMallocBlock__: 0x600000052c00>
<__NSStackBlock__: 0x7fff5a18b7e0>
注意看它们的地址,NSGlobalBlock的地址明显要短,因为它是在进程数据段上的。
blockC则是强行用__weak声明让其分配在栈上,这里会看到一个黄色的警告,大意就是指分配后就会被释放。就是说viewDidLoad这个方法return后这个block就会被释放。
动态分配和静态分配的区分是在哪里?观察一下就发现NSGlobalBlock类型是没有捕获局部变量的,它只是打印一一个字符串。通过NSString literal创建的字符串是放在常量区的,也就是数据段上。全局的block里没有引用任何堆或栈上的数据。另外如果将上面的例子中的int value = 10;改为const int value = 10;那么blockB将变成NSGlobalBlock,这是因为const修饰下value里的值会存储在常量区即数据段上,也就是不违反原则,只要block literal里没有引用栈或堆上的数据,那么这个block会自动变为NSGlobalBlock类型,这是编译器的优化。
在属性声明上,我们一般会用copy修饰一个Block属性。原因是什么?
在MRR或MRC(两个词都是指同一个玩意)中,block默认是在栈上创建的。如果我们将它赋值给一个成员变量,如果成员变量没有被copy修饰或在赋值的时候没有进行copy,那么在使用这个block成员变量的时候就会崩溃。
思考一下下面的代码。
@property(nonatomic, weak) void(^block)();
- (void)viewDidLoad {
[superviewDidLoad];
int value = 10;
void(^blockC)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockC);
_block = blockC;
}
- (IBAction)action:(id)sender {
NSLog(@"%@", _block);
}
这代码在MRC下是会崩溃的。但ARC下就不会了,因为block默认就创建在堆上了。但是不是意味着ARC不用写copy来修饰block属性呢?当然不是了,上面已经说了,我们是可以强行在ARC上将一个block创建在栈上的。