Block 有三种类型

2018-08-09  本文已影响0人  昵称nickName

内存五大内存分区-----堆、栈、自由存储区、全局/静态存储区和常量存储区.
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创建在栈上的。

上一篇下一篇

猜你喜欢

热点阅读