37、理解“块”这一概念
“块”是一种可在C、C++ 及 Objective-C 代码中使用的“词法闭包”,它极为有用,这主要是因为借由此机制,开发者可将代码像对象一样传递,令其在不同环境下运行。还有个关键的地方是,在定义“快”的范围内,它可以访问到中的全部变量。
下面是一个最简单的块:
^{
//Block implementation here
}
块其实就是个值,而且自有其相关类型。与 int、float 或 Objective-C 对象一样,也可以把块赋给变量,然后像使用其他变量那样使用它。块类型的语法与函数指针近似。
默认情况下,为块所捕获的变量,是不可以在块里修改的。声明变量的时候可以加上 __block 修饰符,这样就可以在块内修改了。
如果将块定义在 Objective-C 类的实例方法中,可以访问类的所以实例变量和self变量。块总能修改实例变量,所以在声明时无须加 __block。
self 也是个对象,因而块在捕获它时也会将其保留,如果 self 所指代的那个对象同时也保留了块,那么这种情况通常会导致“保留环”。
定义块的时候,其所占的内存区域是分配在栈中的,这就是说,块只在定义它的范围内有效,例如,下面这段代码就有危险:
void (^block)();
if (/* some condition */ ) {
block = ^{
NSLog(@"Block A");
};
} else {
block = ^{
NSLog(@"Block A");
};
}
block();
定义在if及else语句中的两个块都分配在栈内存中.编译器会给每个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能把分配给块的内存覆写掉,于是,这两个块只能保证在对应的if或else语句范围内有效.这样写出来的代码可以编译,但运行起来时而正确,时而错误.
为解决此问题,可给块对象发送copy消息以拷贝之.这样的话,就可以把块从栈复制到堆了.拷贝后的块,可以在定义它的那个范围之外使用.
明白这点后,我们只需给代码加上两个copy方法调用,就可以令其变的安全了:
void (^block)();
if (/* some condition */ ) {
block = [^{
NSLog(@"Block A");
} copy];
} else {
block = [^{
NSLog(@"Block A");
} copy];
}
block();
38、为常用的块类型创建 typedef
以 typedef 重新定义块类型,可令块变量用起来更加简单.
定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突.
不妨为同一个块签名定义多个类型别名.如果要重构的代码使用了块类型的某个别名,那么只需修改相应 typedef 中的块签名即可,无需改动其他 typedef.
39、用 handler 块降低代码分散程度
在创建对象时,可以使用内联的 handler 块将相关业务逻辑一并声明.
在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若改用 handler 块来实现,则可直接将块与相关对象放在一起.
设计 API 时如果用到了 handler 块,那么可以增加一个参数,使调用者可以通过此参数来决定应该把块安排在哪个队列上执行.
40、用块引用其所属对象时不要出现保留环
如果块所捕获的对象直接或间接地保留了块本身,那么就得担心保留环问题.
一定要找个适当的时机解除保留环,而不能把责任推给 API 的调用者.