Apple 在C, Objective-C, C++加上Block这个延申用法。目前只有Mac 10.6 和iOS 4有支援。Block是由一堆可执行的程式组成,也可以称做没有名字的Function (Anonymous function)。如果是Mac 10.6 或 iOS 4.0 之前的平台可以利用 http://code.google.com/p/plblocks/ 这个project得以支援Block语法。 Apple有一个叫做GCD(Grand Central Dispach)的新功能,用在同步处理(concurrency)的环境下有更好的效率。Block语法产生的动机就是来自于GCD,用Block包好一个工作量交给GCD,GCD有一个宏观的视野可以来分配CPU,GPU,Memory的来下最好的决定。
Block 简介
Block其实行为和Function很像,最大的差别是在可以存取同一个Scope的变数值。 Block 实体会长成这样
^(传入参数列) {行为主体};
Block实体开头是"^",接着是由小括号所包起来的参数列(比如 int a, int b, float c),行为的主体由大括号包起来,专有名词叫做block literal。行为主体可以用return回传值,型别会被compiler自动办识出来。如果没有参数列要这样写(void)。 看个列子
^(int a) {return a*a;};
这是代表Block会回传输入值的平方值(int a
int result = ^(int a) {return a*a;} (5);
很怪吧。后面小括号里的5 会被当成a的输入值然后经由Block输出5*5 = 25指定给result这个变数。 有没有简单一点的方法不然每次都要写这么长?有。接下来要介绍一个叫Block Pointer的东西来简化我们的写法。 Block Pointer是这样宣告的
回传值
直接来看一个列子
int (^square) (int);
// 有一个叫square的Block Pointer,其所指向的Block是有一个int 输入和 int 输出
square = ^(int a ) {return a*a ;}; // 将刚刚Block 实体指定给 square
使用Block Pointer的例子
int result = square(5); // 感觉上不就是funtion的用法吗?
也可以把Block Pointer当成参数传给一个function,比如说
void myFuction( int (^mySquare) (int) ); // function 的宣告,
传入一个有一个int输入和int输出的Block 型别的参数 呼叫这个myFunction的时候就是这样呼叫
int (^mySqaure) (int) = ^(int a) {return a*a;};
// 先给好一个有实体的block pointer叫mySquare
myFunction( mySqaure ) ; //把mySquare这个block pointer给myFunction这个function
或是不用block pointer 直接给一个block 实体,就这样写
当成Objective-C method 的传入值的话都是要把型别写在变数前面然后加上小括号,因些应该就要这样写
-(void) objcMethod:(
读文至此是不是对Block有基本的认识?接下来我们要谈谈Block相关的行为和特色 首先是来看一下在Block里面存取外部变数的方法
存取变数
1. 可以读取和Block pointer同一个scope的变数值:
{
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
int result = myPtr(3); // result is 11
}
我们再来看一个很有趣的例子
{
int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
outA = 5;
int result = myPtr(3); // result 的值还是
}
{
}
原本mutableArray的值是{@"one",@"two",@"three"}在block里被更改mutableArray所指向的物件后,mutableArray的值就会被成{@"one",@"two"}
2. 直接存取static 的变数
{
static int outA = 8;
int (^myPtr) (int) = ^(int a) {return outA+a;};
// block 里面可以读同一个scope的outA的值
outA = 5;
int result = myPtr(3); // result 的值是
}
甚至可以在block里面直接改变outA的值比如这样写
{
static int outA = 8;
int (^myPtr) (int) = ^(int a) { outA= 5; return outA+a;};
// block 里面改变outA的值
int result = myPtr(3); // result 的值是
}
3. Block Variable 在某个变数前面如果加上修饰字__block 的话(注意block前有两个下底线),这个变数又称为block variable。那么在block里就可以任意修改此变数值,变数值的改变也可以知道。
{
}
因为myPtr和myPtr2都有用到num这个block variable,最后result的值就会是7
生命周期和记忆体管理
因为block也是继承自NSObject,所以其生命周期和记忆体的管理也就非常之重要。
block一开始都是被放到stack里,换句话说其生命周期随着method或function结束就会被回收,和一般变数的生命周期一样。
关于记忆体的管理请遵循这几个要点
1. block pointer的实体会在method或function结束后就会被清掉
2. 如果要保存block pointer的实体要用-copy指令,这样block pointer就会被放到heap里
typedef int (^MyBlock)(int);
MyBlock genBlock();
int main(){
}
MyBlock genBlock() {
}
此程式由genBlock里产生的block再指定给main function的outBlock变数,执行 这个程式会得到 Segmentation fault (注:有时候把 genBlock里的a 去掉就可以跑出结果的情形,这是系统cache住记忆体,并不是inBlock真得一直存在,久了还是会被回收,千万不要以为是对的写法) 表示我们用到了不该用的记忆体,在这个例子的情况下是在genBlock里的inBlock变数在return的时候就被回收了,outBlock无法有一个合法的记忆体位置-retainCount就没意义了。 如果这个时候需要保留inBlock的值就要用-copy指令,将genBlock改成
}
这样[inBlock copy]的回传值就会被放到heap,就可以一直使用(记得要release) 执行结果是 result is 1 result is 15 再次提醒要记得release outBlock。 如果一回传[inBlock copy]的值就不再需要的时候可以这样写
}
-copy指令是为了要把block 从stack搬到heap,autorelease是为了平冲retainCount加到autorelease oop ,回传之后等到事件结束就清掉。 接下来是block存取到的local variable是个物件的型别,然后做copy 指令时
MyBlock genBlock() {
}
结果会印出 retain count of string 2 这个结果和上面2.3提到的一样,local variable被retain了 那再来试试2.4,在local variable前面加上__block
MyBlock genBlock() {
}
执行的结果就是会 retain count of string 1
Block Copying注意事项
如果在Class method里面做copying block动作的话 1. 在Block里如果有直接存取到self,则self会被retain 2. 在Block里如果取存到instance variable (无论直接或是从accessor),则self会被retain 3. 取存到local variable所拥有的object时,这个object会被retain 让我们来看一个自订的Class
@interface
}
-(void) logName;
@end
@implementation
-(id) initWithTitle:(NSString * ) newTitle{
}
-(void) logName{
}
-(void ) dealloc{
}
@end
在main 里使用如下
-(id) initWithTitle:(NSString * ) newTitle{
}
在Block主体里用newTitle这个变数而不是title。这样self就不会被retain了。
最后谈一个小陷井
void (^myLog) (void);