Block
对象是一个C
语言层级的语法和runtime
的特性。Block
类似于标准C
函数,但除了可执行代码外,它还可能包含了自动(栈stack
)或托管(堆heap
)的内存所绑定的变量。因此,Block
可以保存一组状态(数据),当执行它时可以使用这些状态(数据)来影响行为。
可以使用
Block
来编写函数表达式,这些函数表达式可以传递给API
,可以将其存储并由多个线程使用。Block
作为回调特别有用,因为Block
既包含回调时要执行的代码,也包含执行过程中所需的数据。block runtime
是开源的,可以在LLVM’s compiler-rt子项目存储库中找到,或Open Sourece查看,或者下载源码。Block
也提交给C
标准工作组,由于Objective-C
和C++
都从C
派生而来,所以Block
被设计成可以在这三种语言中运行(以及Objective-C++
)。
Block的基本使用
创建一个block
时,^
运算符指示block
字面量表达式的开头,它的后面可能跟着一个包含在()
中的参数列表。Block
的主体包含在{}
中。block
定义语法如下:
1 | <returnType>(^<blockName>)(<parameterTypes>) = ^(<parameters>) { |
1、定义有参数和无参数的block
1 | // 1、声明block变量,block变量保存对block的引用。 |
2、定义block
时返回值类型通常省略
如果没有显式声明Block
表达式的返回值,则可以从块的内容自动推导出返回值。
1 | void (^block4)(NSString *) = ^ void (NSString *str){ |
3、block的类型
1 | // block4的类型:int(^)(NSString *) |
4、通常,block
用作为参数传递给方法或函数。这样的情况下,通常创建一个内联块(block "inline"
),而不需要声明
1 | - (void)doSome:(NSInteger (^)(NSString *))doBlock { |
5、typedef
关键字给定义的block
类型取别名,定义语法如下:
1 | typedef <returnType>(^<name>)(<arguments>); |
1 | typedef float(^CalculateBlock)(float, float); |
6、全局block
在文件层面,可以定义全局block
字面量表达式
1 |
|
Block 的概念
Block
对象提供了一种创建特殊函数体的方法,函数体可用C
和C衍生
语言(如Objective-C
和C++
)作为表达式。在其他语言和环境中,Block
对象有时也称为“闭包”(closure
)。
Block的功能
Block
是一个匿名的内联代码块,它包含:
- 和函数一样具有类型化的参数列表
- 具有推断或声明的返回类型
- 可以在它定义的词法范围内 捕获 状态
- 可以有选择地修改词法范围的状态
- 可以与在相同的词法范围内定义的其它
Block
共享修改状态 - 在词法作用域(
stack
)被销毁后,可以继续共享和修改在词法作用域(stack
)中定义的状态
可以copy
一个Block
,甚至将它传递给其他线程以延迟执行(或本线程的执行循环里)。编译器 和 runtime
会把Block
用到的所有变量保存到Block
的所有副本的生命周期后。
注意:尽管
Block
可以用于纯C
和c++
,但是Block
也是一个Objective-C
对象。
Block
回调
Block
通常表示小段的,独立的代码块。因此,它们在封装并发执行的工作单元、集合中的项或在另一个操作完成时作为回调函数特别有用。
Block
是传统回调函数的有用替代方法,主要有两个原因:
允许你在方法调用的地方编写
block
代码,并稍后在方法实现的上下文中执行。
因此,Block
通常是框架方法的参数。block
允许访问局部变量。
相比于回调函数需要一个在执行回调操作所需的所有上下文信息的数据结构,block
可以直接访问本地变量。
block 和 变量
block使用的变量类型
在 Block
对象的代码体中可以引用三种标准类型的变量,就像函数传参一样:
- 全局变量,包括静态局部变量;
- 全局函数,(从技术上讲,它不是变量);
- 作用域中的局部变量和参数;
Block
还支持另外两种类型的变量:
- 在函数级别上是
__block
变量。 它们在Block
(和作用域)内是可变的,如果将引用的Block
复制到堆(heap
)中,则将保留它们。 const
导入
以下规则适用于 Block
中使用的变量:
1、 可以访问全局变量,包括存在于作用域内的静态(static
)变量。
2、传递给 Block
的参数是可访问的(就像函数的参数一样)。
3、词法作用域内的栈(stack
,或者非静态)变量被捕获为常量(const
)变量。
它们的值在程序内的Block
表达式处获取。在嵌套Block
中,从最近的封闭范围中捕获值。
4、用__block
存储修饰符声明的词法作用域内的局部变量,是传递引用的,因此是可变的。
任何更改都将反映在词法作用域内,包括在同一词法作用域内定义的任何其他block
。
5、在block
的词法作用域内声明的局部变量,其行为与函数中的局部变量完全相同。block
的每次调用都提供该变量的新副本。这些变量又可以在块中作为常量(const
)使用,或在block
内包含的block
中的引用变量。
__block存储类型
可以在
block
内部读取导入变量的值,直接修改变量的值会报错。可以通过__block
存储类型修饰符来指定导入到block
内部的变量是可变的(即可读写)。__block
存储方式类似于局部变量的register
、auto
和static
存储类型,但它们相互排斥。__block
变量存在于存储中,该存储在变量的词法范围与在该变量的词法范围内声明或创建的所有block
和block
副本之间共享。给定词法作用域中的多个block
可以同时使用一个共享变量。
注意:作为一种优化,
block
存储从栈(stack
)开始,就像block
本身一样。如果block
是用Block_copy
(或在Objective-C
中发送copy
)复制的,则会将block
变量复制到堆(heap
)。因此一个__block
变量的地址会随时间改变。
__block
变量还有两个严格的限制:它们不能是可变长度数组,也不能是包含C99
可变长度数组的结构体。
1、block
内部使用 (static
、non-static
、__block
修饰的)局部变量
- (
static
和__block
修饰的)局部变量是引用传递; non-static
的局部变量是值传递;
1 | int a = 3; |
2、block
中使用全局变量
- 在
block
中使用全局变量是引用传递
1 | int a = 2; |
对象和block变量
block
提供对Objective-C
和C++
对象以及其他block
作为变量的支持。
Objective-C对象
当复制block
时,它会创建对在block
中使用的对象变量的强引用。如果在方法的实现中使用block
:
- 如果通过引用访问实例变量,则会对
self
进行强引用。 - 如果按值访问实例变量,则会对该变量进行强引用。
1 | void (^testBlock)(void) = ^{ |
C++对象
一般来说,可以在block
内使用C++
对象。在成员函数中,对成员变量和函数的引用是通过隐式导入该指针实现的,因此看起来是可变的。如果block
被复制,有两个注意事项:
如果你有一个
__block
存储类来存储基于栈(stack
)的C++
对象,那么将使用常规copy
构造函数。如果在
block
中使用任何其他基于c++
栈(stack
)的对象,则它必须具有const
的copy
构造函数。然后使用该构造函数复制c++
对象。
block对象
当您复制一个block
时,如果需要,将复制该block
中对其他block
的任何引用——可以复制整个树(从顶部开始)。如果您有block
变量,并且从该block
中引用了一个block
,那么该block
将被复制。
Block Tips
避免的模式
block
字面量(即:^{ ... }
)是表示Block
栈(stack
)本地数据结构的地址。因此,栈(stack
)本地数据结构的范围是封闭的复合语句,因此应避免以下示例中显示的模式:
1 | void (^blockArray[3])(void); // 定义包含3个block引用的数组 |
block存放区域
在block runtime
中,定义了6种类:
1 | BLOCK_EXPORT void * _NSConcreteStackBlock[32]; 栈上创建的block;由系统自动分配和释放,作用域执行完毕之后就会被立即释放 |
在MRC环境下
如果block
没有引用外部的局部变量,block
放在全局区
如果block
引用了外部的局部变量,block
就放在栈里面
在MRC
环境下,block
只能使用copy
,不能使用retain
。使用retain
,则block
存放在栈里面。使用 copy
会把 block
从栈空间复制到堆空间。
在ARC环境下
1 | void (^globalBlock)(void) = ^{ }; |
1 | int a = 2; |
当
block
中使用了外部的 非静态的 和__block
修饰的 局部变量,block
存储在堆区;如果使用了 静态变量 和 全局变量 则存放在全局区。ARC环境下,
block
用strong
和copy
修饰效果是一样的。
具体可以参考掘金 探寻block的本质。
block循环引用(Retain Circle)
通常我们会在某个类中定义一个block
类型的变量,当在该类中使用block
时用到了该类的实例变量。这时候block
对象会与该对象相互持有发生循环引用,引起内存泄漏的问题。如下代码所示:
1 | - (void)doSome { |
1、可以通过__weak
修饰符解决循环引用:
__weak
修饰符的变量被Block
捕获时是对其进行弱引用持有的,可以避免循环引用。
1 | // 方式一:__weak ClassName |
我们通常会定义一个宏:
1 |
|
2、__weak
和 __strong
当block
执行时,如果__weak
修饰的变量被提前释放了,则会出现野指针,导致内存泄漏。
如果不允许在block
执行时self
被释放,则需要在block
内部使用__strong
修饰一个局部变量来接收weakSelf
,防止weakSelf
提前释放,strongSelf
会对self
产生一个强引用,当block
执行结束时便会释放。
1 | __weak typeof(self) weakSelf = self; |
3、@weakify、@strongify
@weakify
、@strongify
,这两个关键字是RAC
(ReactiveCocoa)中避免 Block
循环引用而开发的2个宏
其实 @weakify(self)
和 @strongify(self)
就是比我们日常写的 weakSelf
、strongSelf
多了一个 @autoreleasepool{}
而已,使用者两个宏,需要引入RAC
的 EXTScope.h
和 metamacros.h
源文件。
1 | id foo = [[NSObject alloc] init]; |
并不是所有block里面都用weakSelf
在开发中,我们并不需要一定使用 weakSelf
,这取决于是否存在循环引用。
1、系统的一些回调方法
1 | [UIView animateWithDuration:0.25 animations:^{ |
UIView 的某个负责动画的对象持有了 block,而block 持有了 self。因为 self 并不持有 block,所以就没有循环引用产生,因为就不需要使用 weak self 了。
2、常用的第三方库
1 | [self.bgView mas_makeConstraints:^(MASConstraintMaker *make) { |
通过查看Masonry
的源代码,在执行过程中会创建MASConstraintMaker
类型的变量持有 self.bgView
,而这个block
并没有被任何对象持有,因此在 Masnory
中使用 self.xxx 不会循环引用。
参考博客:
深入研究 Block 用 weakSelf、strongSelf、@weakify、@strongify 解决循环引用