理解“块”这一概念

概述:在开发应用程序时应留意多线程问题。也许你觉得自己开发中用不到多线程,即便如此它也可能依然是多线程,因为系统框架通常会在UI线程之外另起一些线程来执行任务。开发时最糟糕的事情莫过于程序因UI线程阻塞而挂起了,这样应用体验会很差。
在Mac OS X系统中,这将使鼠标指针一直在旋转,而在iOS系统中,阻塞过久的程序可能会终止。
所幸苹果公司以全新的方式设计了多线程。当前多线程的核心就是“块”(block)与“大中枢派发”(Grand Central Dispatch,GCD)。这虽然是两种不同的技术,但它们是一并引入的。
“块”是一种可以在C、C++及Object-C代码中使用的“词法闭包”,它极为有用,这主要是因为皆由此而机制,开发者可将代码像对象一样传递,令其在不同的环境下运行。还有个关键的地方是,在定义“块”的范围内,它可以访问到其中的全部变量。
GCD是一种与“块”有关的技术,它提供了对线程的抽象,而这种抽象则基于“派发队列”。开发者可以将“块”排入队列中,由GCD负责处理所有调度事宜。GCD会根据系统资源情况,适时地创建、复用、摧毁后台线程,以便处理每个队列。此外,使用GCD还可以方便地完成常见的编程任务,比如编写“只执行一次的线程安全代码”,或者根据可用的系统资源来并发执行多个操作。
“块”与GCD都是当前OC编程的基石。因此必须理解其原理及功能。本篇文章只介绍“块”。

一.什么是“块”
Block是在iOS4.0之后引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调。
Block类型语法:

return_type (^block_name) (parameters)

Block表达式语法:^ 返回值类型 (参数列表) {表达式}

^int(int a, int b){
    return a+b;
};

其中可省略部分有:
返回类型

^(int a, int b){
    return a+b;
};

参数列表为空

^{
    NSLog(@"No Params");
};

极简模式:^{表达式};

定义块变量:

int (^addBlock) (int a, int b) = ^(int a, int b) {
    return a+b;
};

调用:

addBlock(2,5);

二.“块”有什么作用
“块”的强大之处:在声明它的范围内,所有的变量都可以为其捕获,也就是说在其范围内的全部变量,在块内依然可用。
如下:

int addition = 10;
int (^addBlock) (int a, int b) = ^(int a, int b) {
    return a+b+addition;
};
addBlock(2,5);//17

默认情况下,为块所捕获的变量,在块里面是不可以修改的。如果改动,编译器就会报错。不过声明变量的时候加上__block修饰符就可以修改了。

三.“块”的内部结构是什么样子的
每个OC对象都占据着某个内存区域。因为实例变量的个数及对象所包含的关联数据互不相同,所以每个对象所占的内存区域也有大有小。“块”本身也是对象,在存放“块”对象的内存区域中,首个变量是指向Class对象的指针该指针叫isa。其余内存里含有“块”对象正常运转所需的各种信息。“块”对象的内存布局:
屏幕快照 2018-07-02 上午10.38.41.png
在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码。函数原型至少要接受一个void*型的参数,此参数代表“块”。“块”其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态。而改用“块”之后,则可以把原来用标准C语言特性所编写的代码封装成简明且易用的接口。
descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针辅助函数在拷贝及丢弃块对象时运行,其中会执行一些操作,比方说,前者要保留捕获的对象,后者则将之释放。
“块”还会把它所捕获的所有变量都拷贝一份。这些拷贝放在descriptor后面,捕获多少变量就要占据多少内存空间。请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量。invoke函数为什么需要把块对象作为参数传进来呢?原因在于,执行块时,要从内存中把这些捕获到的变量读取出来。

四.“块”的几种类型:全局块、栈块及堆块

五.使用“块”的注意点
防止block循环引用,block循环引用的情况:某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,如下:

self.myBlock = ^(Type var){
    [self dosomething];
};

解决方法:(1)在ARC下:使用__weak

__weak typeof(self) weakSelf = self;
self.myBlock = ^(Type var){
    [weakSelf dosomething];
};

(2)MRC下:使用__block

__block typeof(self) blockSelf = self;
self.myBlock = ^(Type var){
    [blockSelf dosomething];
};

值得注意⚠️的是在ARC的情况下使用__block也有可能导致循环引用

六.总结