1. 前言
Blocks是C语言的扩充功能,可以用一句话来表示Blocks的扩充功能:带有自动自动变量的匿名函数。你可以用block来组合函数表达式,可以将block传递给API,可以在多线程环境使用block和存储block。block包括执行代码以及执行代码期间需要的数据,block可以作为回调函数。block也称为“闭包”。
2. Getting Started with Blocks
2.1 Declaring and Using a Block
你可以使用^操作符来声明一个block variable。block body是在{}内。下面一个例子是定义了block variable。
int multiplier = 7;
int (^myBlock)(int) = ^(int num){
return num * multiplier;
};
下面是关于上述代码的一些描述,注意到block能够使用同一作用域的变量multiplier。
当你定义了一个block变量,则可以把它当作函数一样使用。
printf("%d", myBlock(3));
// prints "21"
2.2 Using a Block Driectly
在多数情况下,你不需要定义block variables。你可以使用block的字面定义直接作为参数。如下面所示:可以看出qsort_b函数的第三个参数就是block的字面定义,而没有定义一个block variable。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
});
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
当然你也可以这样做,定义一个block变量,然后将其作为qsort_b函数的第三个参数。
char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
(void) (^myBlock) (const void *, const void *) = ^(const void *l, const void *r) {
char *left = *(char **)l;
char *right = *(char **)r;
return strncmp(left, right, 1);
};
qsort_b(myCharacters, 3, sizeof(char *), myBlock);
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }
3. Declaring and Creating Blocks
3.1 Declaring a block Reference
block variable引用一个block,就好像定义一个函数指针,只是用^代替*。下面是所有block variable声明。
void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Block支持可变参数,一个不需要参数的block必须指定void在参数列表,且返回值为void,也必须在代码中写出。下面的例子就是错误的。
int (^myBlock); //参数为空,但没有明确指出void在参数列表。
(^myBlock1r) (void); //返回值为空,但没有指出返回值为void
你也可以使用typedef来定义block variable。
typedef float (^MyBlockType)(float, float);
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;
3.2 Creating a Block
用^开头开表示一个block对象的开头,它也许会跟着参数列表(用()表示),block body则用{}表示,最后body后面用;结尾。如下面所示,定义一个名为oneFrom的block variable。在将block字面表达式赋值给它,也可以说oneFrom引用了该block。如果你不显式的写出block的返回值类型,则会自动根据body中return语句的表达式计算结果的类型来作为返回值类型。如果return type是根据return 表达式推断的,且参数列表是void,则可以不写void作为参数列表。如果body中有多个return表达式,则每个表达式返回的类型必须相同(using casting if necessary)。
float (^oneFrom)(float);
oneFrom = ^(float aFloat) {
float result = aFloat - 1.0;
return result;
};
3.3 Global Blocks
在文件级别,你可以定义一个全局block variable。
#import <stdio.h>
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };
Blocks and Variables
这个小节主要介绍block和variable之间的交互,包括内存管理。
Types of Variable
在block body的代码,variable可以被分成五种。
- 全局变量,包括全局静态变量。
- 全局函数
- 在同一作用域的局部变量和参数
- __block variables,它们在block body内是可变的,并且如果引用了该变量的block被copy到堆上,这些变量会一直存在。
- const imports
下面是block body内使用变量的一些原则,
- 全局变量(包含静态)是可以访问
- 全局函数可以访问
- 局部变量(包含静态),且声明在block之前。
- block能够访问的变量,如果是在栈上分配的,则默认它的cv-qualifer是const。也就是不能够修改。
- 在block body内的variable,可以看作函数内部的局部变量,每次调用block,这些变量都会被copy。
- 被
__block修饰的非static局部变量,能够被修改。
int GlobalVariable = 1;
static int staticGlovalVariable = 2;
void globalFunction(void){
printf("myBlock can assess staticGlobalFuncion\n");
}
int main(int argc, const char * argv[]) {
int outScopeLocalVariable = 3;
static int staticOutScopeLocalVariable = 4;
{
static int staticLocalVariable = 5;
int LocalVariable = 6;
int __block blockLocalVariable = 7; //block type
void (^myBlock)(int) = ^(int parameter){
printf("%d\n", GlobalVariable);
printf("%d\n", staticGlovalVariable);
printf("%d\n", outScopeLocalVariable);
printf("%d\n", staticOutScopeLocalVariable);
printf("%d\n", LocalVariable);
printf("%d\n", staticLocalVariable);
printf("%d\n", blockLocalVariable);
printf("%d\n", parameter);
globalFunction();
GlobalVariable = 1;
staticGlovalVariable = 1;
//outScopeLocalVariable = 1; //不能修改
staticOutScopeLocalVariable = 1;
//LocalVariable = 7; //不能修改
staticLocalVariable = 1;
blockLocalVariable = 1; //能够被修改
parameter = 1;
//修改之后的输出
printf("%d\n", GlobalVariable);
printf("%d\n", staticGlovalVariable);
printf("%d\n", outScopeLocalVariable);
printf("%d\n", staticOutScopeLocalVariable);
printf("%d\n", LocalVariable);
printf("%d\n", staticLocalVariable);
printf("%d\n", blockLocalVariable);
printf("%d\n", parameter);
};
myBlock(8);
}
return 0;
}
输出结果如下。
3.3 Object and Block Variables
3.3.1 Objective-C Objects
当一个block被copy后,当你在block中使用一个调用函数或方法时,使用一个OC Object时,它会创建一个强引用,根据情况不同,引用指向的对象不同。 有两种情况,一种是传值,另一种是传引用。 第一种情况,直接传递一个实例变量,那么直接引用self。第二种情况,传递实例变量通过值,则引用该variable。
dispatch_async(queue, ^{
// instanceVariable NSString is used by reference, a strong reference is made to self
doSomethingWithNSString([[NSString alloc] init]);
});
NSString *localVariable = [[NSString alloc] init];
dispatch_async(queue, ^{
/*
localVariable is used by value, a strong reference is made to localVariable
(and not to self).
*/
doSomethingWithObject(localVariable);
});
3.3.2 C++ Objects
如果你在block里使用C++对象,当block copy后,需要注意如下两点。
- 如果是__block 修饰的c++栈对象,这时候,c++对象的拷贝构造函数被调用。
- 如果不是__block 修饰的c++栈对象,则它必须有一个const拷贝构造函数。c++对象拷贝时,使用该拷贝构造函数。
3.3.3 Blocks
当你在block内部引用了其它blocks时,如果该block被copy,那么内部的blocks也会被copy。如果在block内部使用了block variable,则该block variable引用的block也会被copy。
4. Others
4.1 Copying Blocks
一般情况下,你不需要copy或者retain block。当你需要在声明block的作用域被销毁后使用该块,才需要复制,复制将block到堆内存上。可以分别使用两个C函数来进行copy和release block。为了防止内存泄露,你必须balance Block_copy和Block_release操作。
Block_copy();
Block_release();
4.2 Patterns to Aviod
一个block字面值是一个在栈上的局部数据结构地址,用来代表一个block。那么它的作用范围就是在最内层{}范围内。下面是两个错误的使用block的例子。
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 block references
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: The block literal scope is the "for" loop.
}
}
void dontDoThisEither() {
void (^block)(void);
int i = random():
if (i > 1000) {
block = ^{ printf("got i at: %d\n", i); };
// WRONG: The block literal scope is the "then" clause.
}
// ...
}
5. 总结
- block的定义以及一些错误的写法
- block与变量的交互和内存管理。
- __block type的使用。
- block内使用OC,C++和block Objects。