Block简介
block可以说是一种匿名函数,但是又以对象的形式存在,内部使用结构体实现,我们可以将一个block作为参数传给其它的函数或者block. 一个简单的block如下:
^(NSInteger param) {
NSLog(@"%d", param);
};
其它block写法参考: fuckingblocksyntax.com
Block类型
我们都知道block有三种类型
- NSGlobalBlock, 全局block
- NSStackBlock, 栈区block
- NSMallocBlock, 堆区block
那么是什么条件决定了block的种类呢? 也就是说是什么条件决定了block存在的内存区域呢? 下面就通过实践来探索下结果.
:::tip iOS的内存存储区域: 栈区、堆区、全局/静态区、常量区、代码区 :::
Block实例
我们声明一个block,什么都不关联,看看默认情况下是什么类型
NSLog(@"%@", ^(NSInteger a) {
});
// TestBlockMemory[4885:374356] <__NSGlobalBlock__: 0x1000010c0>
通过实例我们可以看出,最最简单的block是NSGlobalBlock类型,存在全局区.
当block引用了外部变量
NSInteger num = 10;
NSLog(@"%@", ^(NSInteger a) {
return a + num;
});
// TestBlockMemory[4918:378440] <__NSStackBlock__: 0x7ffeefbff4b8>
首先测试的是引用了一个局部变量,结果为NSStackBlock, 在栈区, 如果引用全局变量呢?
static NSInteger num = 10;
NSLog(@"%@", ^(NSInteger a) {
return a + num;
});
// TestBlockMemory[4941:379770] <__NSGlobalBlock__: 0x1000010d8>
看的出,引用全局变量后并没有像局部变量那样改变了block原始类型,依然是NSGlobalBlock类型. 在全局区.
block赋值操作
NSInteger num = 10;
NSInteger (^addNum)(NSInteger a) = ^NSInteger(NSInteger a) {
return a + num;
};
NSLog(@"%@", addNum);
// TestBlockMemory[5002:387216] <__NSMallocBlock__: 0x10055a710>
终于出现了堆区
static NSInteger num = 10;
NSInteger (^addNum)(NSInteger a) = ^NSInteger(NSInteger a) {
return a + num;
};
NSLog(@"%@", addNum);
// TestBlockMemory[5025:388490] <__NSGlobalBlock__: 0x100001058>
虽然有了赋值操作,但是引用的是全局变量,block又变回了全局类型.
总结
通过以上这些示例,可以得出结论
- 当block刚创建好时,不引用变量或者引用的是全局变量,不进行赋值时是NSGlobalBlock类型,全局区block.
- 当block引用了外部局部变量,不进行赋值操作时是NSStackBlock类型,栈区block.
- 当block引用了外部局部变量,且进行了赋值操作时是NSMallocBlock类型,堆区block.
其实block在哪个区都是有它特定的意义的,当系统不知道你怎么使用时,就把它放到全局区;系统能明确管理的时候,就把它放到栈区; 当你进行了赋值操作,系统可能认为你在将来某个点会用到这个block,于是就放到堆区,延长它的生命周期.
接下来
如果想更深入的了解block,可以通过clang命令来将oc代码转为c++代码,看内部是如何实现的,执行命令之后会在当前目录产生一个cpp源代码文件
clang -rewrite-objc main.m