什么是block
block 是iOS在4.0之后新增的程式语法,严格来说block的概念并不算是基础程式设计的范围,对初学者来说也不是很容易了解,但是在iOS SDK 4.0之后,block几乎出现在所有新版的API之中,换句话说,如果不了解block这个概念就无法使用SDK 4.0版本以后的新功能,因此虽然block本身的语法有点难度,但为了使用iOS的新功能我们还是得硬着头皮去了解这个新的程式概念。
block都是一些简短代码片段的封装,适用做工作单元,通常用来做并发任务、遍历以及回调
1 Block 概要
Block 提供我们一种能够将函数程式码内嵌在一般述句中的方法,在其他语言中也有类似的概念称做「closure」,但是为了配合Objective-C的贯例,我们一律将这种用法称为「block」
1.1 Block 的功能
Block 是一种具有匿名功能的内嵌函数,它的特性如下:
如一般的函数般能拥有带有型态的参数。
拥有回传值。
可以撷取被定义的词法作用域(lexical scope)状态。
可以选择性地修改词法作用域的状态。
注:词法作用域(lexical scope)可以想像成是某个函数两个大括号中间的区块,这个区块在程式执行时,系统会将这个区块放入堆叠记忆体中,在这个区块中的宣告的变数就像是我们常听到的区域变数,当我们说block可以撷取同一词法作用域的状态时可以想像block变数和其他区域变数是同一个层级的区域变数(位于同一层的堆叠里),而block的内容可以读取到和他同一层级的其他区域变数。
我们可以拷贝一个block,也可以将它丢到其他的执行绪中使用,基本上虽然block在iOS程式开发中可以使用在C/C++开发的程式片段,也可以在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。
1.2 Block 的使用时机
Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。
在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:
可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。
可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。
2 初探Block
2.1
int (^backValue)(int num);
回类型 | 方法名 | 参数
下面是一个简单Block示例
int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num)
{
return num * multiplier;
};
我们宣告一个「myBlock」变数,用「^」符号来表示这是一个block。
这是block的完整定义,这个定义将会指定给「myBlock」变数。
表示「myBlock」是一个回传值为整数(int)的block。
它有一个参数,型态也是整数。
这个参数的名字叫做「num」。
这是block的内容。
值得注意的地方是block可以使用和本身定义范围相同的变数,可以想像在上面的例子中 multiplier 和 myBlock 都是某一个函数内定义的两个变数也就是这个变数都在某个函数两个大括号「{」和「 }」中间的区块,因为它们的有效范围是相同的,因此在block中就可以直接使用 multiplier 这个变数,此外当把block定义成一个变数的时,我们可以直接像使用一般函数般的方式使用它:
NSLog(@"%d",myBlock(3));//结果会打印出21
2.2 直接使用Block
在很多情况下,我们并不需要将block宣告成变数,反之我们可以直接在需要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中qsort_b函数,这是一个类似传统的qsort_t函数,但是直接使用block做为它的参数:
char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
qsort_b (myCharacters, 3 ,
sizeof ( char *),
^( const void *l, const void *r)//block部分
{
char *left = *( char **)l;
char *right = *( char **)r;
return strncmp (left, right, 1 );
} //end
);
3 注意事项
3.1 Variable Capturing
“Variable Capturing”,直译过来就是捕捉变量。
block会将“捕捉”到的变量复制一份,然后对复制品进行操作
这是非常重要的一点。对于以下代码来说打印结果是b–>2,这是因为在block作用域内会复制a,然后对复制的a进行操作,作用域外的a就不会产生变化,所以,在a++;之后再调用backValue(1),结果是2
int a = 1;
int (^backValue)(int) = ^(int num) {
return num+a;
};
a += 1;
int b = backValue(1);
NSLog(@"b-->%d", b);
如果希望block作用域内可以修改外边的变量,可以使用__block(注意是两个下划线)来修饰int a
。这样以来结果就会是b–>3。
刚才的例子中a是一个基本类型的变量,如果block外是一个oc对象的话,结果就又不一样了,比如下边代码,结果就是”block作用域内赋值”
__block int a = 1;
int (^backValue)(int) = ^(int num) {
return num+a;
};
a += 1;
int b = backValue(1);
NSLog(@"b-->%d", b);
刚才的例子中a是一个基本类型的变量,如果block外是一个oc对象的话,结果就又不一样了,比如下边代码:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 300, 300, 40)];
label.text = @"block作用域外赋值";
void (^backValue)(NSString *) = ^(NSString *str) {
label.text = str;
};
backValue(@"block作用域内赋值");
[self.view addSubview:label];
结果就是”block作用域内赋值”.
这是因为UILabel *label;定义了一个对象指针,在block作用域内复制的是label这个指针,而不是对象,复制之后的指针仍然指向原来的对象,所以对label.text进行操作是可以修改原对象的,但是作用域内如果想下边这样写就不行了,会得到提示Variable is not assignable。