继续这本书的读书笔记,希望在其中也加入自己的一些总结,以加深理解。之前这一章写了很多了,保存到草稿箱中,不知道为何丢失了,真是可惜,看来CSDN的MarkDown编辑器还存在一些bugs,在它打上补丁之前还是写一点发表一下吧。Let’s begin.
多线程编程是每个开发者在开发现代应用程序的时候经常碰到的问题。系统框架经常在你意想不到的时候在UI线程之外使用额外的线程来处理各类工作。没什么比由于UI线程阻塞而导致应用程序挂起更糟糕的了。这种现象表现在Mac OS X上,就会出现一个一直旋转不停的彩球;表现在iOS上,就会因为阻塞太久而导致应用程序被终止。
幸运地是,Apple公司以一种全新的方式来思考多线程问题。现代编程中关键的技术是block和Grand Central Dispatch(GCD),尽管技术迥异。block为C, C++, and Objective-C等语言提供了词法闭包(lexical closures),而且非常有用,主要因为它提供了在不同的上下文环境下就像对待一个对象一样地来传递代码的机制。而且至关重要的是,block可以使用定义它的范围内的所有变量。
GCD基于所谓的派发队列(dispatch queues),为多线程提供了一个抽象机制。block被放置到这些队列里,GCD会为你处理所有的执行计划。GCD可以创建,重用和销毁背景线程,在它认为合适的时候或者基于系统资源状况来处理每个队列。此外,GCD为常规的编程任务提供了易用的解决方案,譬如线程安全和基于目前的系统资源状况来并行处理任务。
第37条:理解“块”(blocks)这一概念
block提供了闭包功能,这一语言特性作为一个扩展被添加到GCC编译器中,存在于所有现代Clang版本中(这个编译器工程被Mac OS X和iOS开发所使用)。block所需的运行时组件在Mac OS X 10.4和iOS 4.0和之后的所有版本中都可用.由于这个语言特性属于C语言级别的特性,因此在C,C++,Objective-C和Objective-C++代码中都可用,这些代码在一个支持该特性的编译器下编译,并运行在block运行时中。
block基础知识
block和函数相似,只不过是定义在另一个函数里面,和定义它的函数共享同一个范围内的东西。block用“^”符号来表示,后面跟着一对花括号,花括号里面是实现代码。
^{
// Block implementation here
}
block是一个数据类型,可以被赋值。与int,float或其他Objective-C对象一样,一个block也可以被赋值到一个变量,与其他类型的变量一样被使用。block类型的语义类似于函数指针。下面是一个无输入参数和返回参数的block例子:
void (^someBlock)() = ^{
// Block implementation here
};
block的强大之处在于:在声明它的范围内,所有变量都可以为其所捕获(capture)。也就是说,在此范围内的所有变量,在block里依然可用。默认情况下,block所捕获的任何变量在block里都不能修改,否则编译器会报错,除非将这样想要被修改的变量声明为__block
修饰符。下面是一个block用来统计数组中小于2的数量的例子:
NSArray *array = @[@0, @1, @2, @3, @4, @5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock: ^(NSNumber *number, NSUInteger idx, BOOL *stop){
if ([number compare:@2] == NSOrderedAscending) {
count++;
}
}];
// count = 2
上面这段代码也展示了内联块(inline block)的用法。在Objective-C引入block之前,想要实现这样的功能只能通过传入函数指针和选择子(selector)的名称,但是这样做需要传入传出状态,经常通过一个“不透明的void指针“(an opaque void pointer)”来实现,从而导致添加额外的代码,也会令方法变得松散。而声明一个内联块,可把所有业务逻辑都放在一处。
当block捕获到一个对象类型的变量时,它会隐式地保留(retain)该对象变量。系统在释放(release)这个block的时候,也会将该对象一并释放。block本身可看做一个对象,也与其他对象一样有引用计数。当最后一个指向block的引用被移除之后,block会被回收,回收时也会释放它所捕获的对象变量,以平衡捕获时所执行的保留操作。
如果block被定义在一个类的实例方法中,则self变量跟该类的所有实例变量一样都可以被block访问。需要特别注意的的是,这些实例变量都可以被block修改,而无需用__block
修饰符来声明。但是,如果一个实例变量被block捕获,无论是读还是写,self变量都会被隐式地捕获,因为实例变量跟self所指代的实例关联在一起。这样就会引起self变量被block所retain。这种情况下,如果block本身再被self所指代的实例retain的话,就会经常导致“保留环”(retain cycles)。
block的内部结构
一个block是一个对象,因为定义它的内存区域中的第一个变量就是一个指向一个Class对象指针,就是所谓的isa指针。这个内存区域的剩余部分包含维持block正常运行的各种信息。一个block对象的内存布局如下:
表一:
Block Layout | |
---|---|
void * | isa |
int | flags |
int | reserved |
void (*)(void *, …) | invoke |
struct * | descriptor |
Captured variables |
表二:
Block Descriptor(上表中的descriptor结构体) | |
---|---|
unsigned |