前言
官方文档 Blocks Programming Topics
1. Block 是什么❓
Block 在其他语言里又称闭包,也可以叫匿名函数,代码块。
Block 是苹果在iOS4开始引入的对C语言的扩展,是用来实现匿名函数的特性。
其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。
2. Block 有什么作用❓
Block 的声明和实现一般不在一个类里,在一个类里也就没什么意思了。
Block 是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊的 Block 还可以保存一段代码,在需要的时候调用,目前 Block 已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调。
3. Block有什么优势❓
个人觉得 Block 优势如下:
第一可以使代码看起来更简单明了,
第二可以取代以前的 delegate 使代码的逻辑看起来更清晰。
4. Block 代码块和普通函数都是一段代码,两者有什么区别❓
- Block代码: 是一个函数对象,是在程序运行过程中产生的;
- 普通函数: 是一段固定代码,产生于编译期;
Block 语法
Block 表达式语法:
^ 返回值类型 (参数列表) {表达式}
注: 1.^ 该操作符被称作"脱字符"
2.Block 的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码
例如:
^ int (int num) {
return num + 1;
};
其中,可省略部分有:
- 可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。例:
^ (int num) {
return num + 1;
};
- 参数列表为空,则可省略,例:
^ {
NSLog(@"No Parameter");
};
即最简模式语法为:
^ {表达式}
巧记 Block 格式
很多人觉得 Block 格式定义很难记,其实我们可以通过与 C 语言中的函数方法对比加强记忆:
int functionName (int a , int b) // C 的方法声明
int (^MyBlockName) (int a , int b) // iOS 的 block 声明
Block 声明
声明 Block 类型变量语法:
返回值类型 (^变量名)(参数列表) = Block 表达式
例如,如下声明了一个变量名为 myBlock 的 Block :
int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
return num * multiplier;
};
printf("%d", myBlock(3));
// prints "21"
该示例的解析如下图:
Block 的定义
/*定义属性,block 属性可以用 strong 修饰,也可以用 copy 修饰 */
@property (nonatomic, strong) void(^myBlock)(); //无参无返回值
@property (nonatomic, strong) void(^myBlock1)(NSString *); //带参数无返回值
@property (nonatomic, strong) NSString *(^myBlock2)(NSString *); //带参数与返回值
//定义变量
void(^myBlock)() = nil; //无参无返回值
void(^myBlock1)(NSString *) = nil; //带参数无返回值
NSString *(^myBlock2)(NSString *) = nil; //带参数与返回值
block 被当做方法的参数
格式:( block 类型)参数名称
- (void)test:(void(^)())testBlock //无参无返回值
- (void)test1:(void(^)(NSString *))testBlock //带参数无返回值
- (void)test2:(NSString *(^)(NSString *))testBlock //带参数与返回值
使用 typedef 定义 block
typedef void(^myBlock)(); //以后就可以使用 myBlock 定义无参无返回值的 block
typedef void(^myBlock1)(NSString *); //使用 myBlock1 定义参数类型为 NSString 的 block
typedef NSString *(^myBlock2)(NSString *); //使用 myBlock2 定义参数类型为 NSString,返回值也为 NSString 的 block
//定义属性
@property (nonatomic, strong) myBlock testBlock;
//定义变量
myBlock testBlock = nil;
//当做参数
- (void)test:(myBlock)testBlock;
Block 的赋值
格式:block = ^返回值类型 (参数列表) {函数主体}
注4: 通常情况下,可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。
//没有参数没有返回值
myBlock testBlock = ^void(){
NSLog(@"test");
};
//没有返回值,void 可以省略
myBlock testBlock1 = ^(){
NSLog(@"test1");
};
//没有参数,小括号也可以省略
myBlock testBlock2 = ^{
NSLog(@"test2");
};
//有参数没有返回值
myBlock1 testBlock = ^void(NSString *str) {
NSLog(str);
}
//省略void
myBlock1 testBlock = ^(NSString *str) {
NSLog(str);
}
//有参数有返回值
myBlock2 testBlock = ^NSString *(NSString *str) {
NSLog(str)
return @"hi";
}
//有返回值时也可以省略返回值类型
myBlock2 testBlock2 = ^(NSString *str) {
NSLog(str)
return @"hi";
}
声明 Block 变量的同时进行赋值
int(^myBlock)(int) = ^(int num){
return num * 7;
};
// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
NSLog(@"I am a aVoidBlock");
};
注:如果没有参数,= 左边用括号表示,= 右边参数可以省略
使用 typedef 定义 Block 类型
在实际使用 Block 的过程中,我们可能需要重复地声明多个相同返回值相同参数列表的 Block 变量,如果总是重复地编写一长串代码来声明变量会非常繁琐,所以我们可以使用 typedef 来定义 Block 类型
// 定义一种无返回值无参数列表的Block类型
typedef void(^MyBlock)();
// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
MyBlock myBlock = ^(){
NSLog(@"MyBlock");
};
// 调用后控制台输出"MyBlock"
myBlock();
截获自动变量值
Block 表达式可截获所使用的自动变量的值。
截获:保存自动变量的瞬间值。
因为是“瞬间值”,所以声明 Block 之后,即便在 Block 外修改自动变量的值,也不会对 Block 内截获的自动变量值产生影响。
int i = 10;
void (^blk)(void) = ^{
NSLog(@"In block, i = %d", i);
};
i = 20;//Block 外修改变量i,也不影响 Block 内的自动变量
blk();//i 修改为20后才执行,打印: In block, i = 10
NSLog(@"i = %d", i);//打印:i = 20
局部自动变量:
在 Block 中只读。Block 定义时 copy 变量的值,在 Block 中作为常量使用,所以即使变量的值在 Block 外改变,也不影响他在 Block 中的值。
int i = 10;
void (^blk)(void) = ^{
// i++; 编译错误,只读
NSLog(@"In block, i = %d", i);
};
static 变量、全局变量:
如果把上个例子的 base 改成全局的、或 static。Block 就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block 在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时 copy 的常量。
static 修饰变量,效果与_ _block一样
__block 说明符号
自动变量截获的值为 Block 声明时刻的瞬间值,保存后就不能改写该值,如需对自动变量进行重新赋值,需要在变量声明前附加 __block
说明符,这时该变量称为__block
变量。
基本类型的 Block 变量等效于全局变量、或静态变量。
例如:
__block int i = 10;//i为__block变量,可在block中重新赋值
void (^blk)(void) = ^{
NSLog(@"In block, i = %d", i);
};
i = 20;
blk();//打印: In block, i = 20
NSLog(@"i = %d", i);//打印:i = 20
自动变量值为一个对象情况
当自动变量为一个类的对象,且没有使用 __block
修饰时,虽然不可以在Block 内对该变量进行重新赋值,但可以修改该对象的属性。
如果该对象是个 Mutable 的对象,例如 NSMutableArray
,则还可以在 Block内对 NSMutableArray
进行元素的增删:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"1", @"2",nil ];
NSLog(@"Array Count:%ld", array.count);//打印Array Count:2
void (^blk)(void) = ^{
[array removeObjectAtIndex:0];//Ok
//array = [NSNSMutableArray new];//没有__block修饰,编译失败!
};
blk();
NSLog(@"Array Count:%ld", array.count);//打印Array Count:1
Block 在内存中的位置
根据Block在内存中的位置分为三种类型 NSGlobalBlock
,NSStackBlock
, NSMallocBlock
。
NSGlobalBlock: 类似函数,位于text段;
NSStackBlock: 位于栈内存,函数返回后Block将无效;
NSMallocBlock: 位于堆内存。
示例1:
BlkSum blk1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"blk1 = %@", blk1);// 打印结果:blk1 = <__NSGlobalBlock__: 0x47d0>
示例2:
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // 打印结果:blk2 = <__NSStackBlock__: 0xbfffddf8>
示例3:
BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // 打印结果:blk3 = <__NSMallocBlock__: 0x902fda0>
blk1
和 blk2
的区别在于:
blk1
没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使blk1与一般函数没有任何区别。
blk2
与 blk1
唯一不同是的使用了局部变量 base
,
注意1:在定义(注意是“定义”,不是“运行”)blk2
时,局部变量 base
当前值被 copy 到栈上,作为常量供 Block 使用。执行下面代码,结果是 203,而不是204。
int base = 100;
base += 100;
BlkSum sum = ^ long (int a, int b) {
return base + a + b;
};
base++;
printf("%ld",sum(1,2));
Block的copy、retain、release操作
对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
NSGlobalBlock: retain、copy、release操作都无效;
NSStackBlock: retain、release操作无效,必须注意的是,NSStackBlock
在函数返回后,Block 内存将被回收。即使 retain 也没用。容易犯的错误是[[mutableAarry addObject:stackBlock]
,在函数出栈后,从 mutableAarry
中取到的 stackBlock
已经被回收,变成了野指针。正确的做法是先将stackBlock
copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]
。支持copy,copy之后生成新的NSMallocBlock
类型对象。
NSMallocBlock:支持 retain、release,虽然 retainCount
始终是1,但内存管理器中仍然会增加、减少计数。copy 之后不会生成新的对象,只是增加了一次引用,类似 retain;
尽量不要对 Block 使用 retain 操作。
几个应用实例:
1、代码用在字符串数组排序
NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];
NSComparator sortBlock = ^(id string1, id string2)
{
return [string1 compare:string2];
};
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];
NSLog(@"sortArray:%@", sortArray);
// 运行结果:sortArray:(
"abc 05",
"abc 1",
"abc 12",
"abc 13",
"abc 21"
)
2、代码块的递归调用
代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,可以递归调用
static void (^ const blocks)(int) = ^(int i)
{
if (i > 0) {
NSLog(@"num:%d", i);
blocks(i - 1);
}
};
blocks(3);
参考:
iOS之Block代码块的定义及使用