Blocks是C语言层级语法和运行时特性。 它们类似于标准C函数,但是除了可执行代码之外,它们还可以保存堆栈变量。 因此,块可以保存数据,在代码执行时使用。
1、Block可以作为函数数调用、作为函数参数、作为方法参数。
2、因为独立完整可以在多线程中使用;
3、因为拥有回调时需要执行的代码和执行代码时需要的数据,常常被用来实现回调Callback。
由于Objective-C和C++都是从C派生,因此三种语言均可以使用(Objective-C使用更多)。iOS从4.0版本开始支持。在其他语言环境中有时被称为“闭包”。
Block是一个匿名的内联代码集合,有以下特点:
1、像函数一样用参数列表
2、有可推断或者声明的返回值类型
3、可以从定义它作用域捕获数据
4、可选的可以修改捕获到的数据
5、同一作用域中的其它block共享捕获的数据
6、作用域的堆栈被销毁后,仍然可以继续共享定义其范围定义的数据
由于compiler 和 runtime 保证block和其相关的数据的生命周期,因此可以copy一份传递到其他地方使用。
首先说明下函数指针的声明格式
返回值类型 ( * 指针变量名) ([形参列表]);
Block variables保存着block的引用。可以像声明函数指针一样声明block变量,但是要把*换成^,例如以下均为有效的声明
void (^blockReturningVoidWithVoidArgument)(void); int (^blockReturningIntWithIntAndCharArguments)(int, char); void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);
Blocks支持可变的参数列表,当没有参数时必须写void。
通过为编译器提供block使用的数据,传递参数,返回值,block设计完全类型安全。可以把block引用强制转换成任意类型的指针,反之亦然。但是不能使用操作符*来访问值,因为block的值在编译期无法计算。
理解:假如每次方法调用当做一次消息发送(一般底层会有很多次),把block中的所以消息发送按照顺序放到一种能够先进先出的数据结构–比方说实现栈特性的结构体;那么block变量和^{ ... }作用一样都是指向结构体的首地址;传递首地址跟其他对象指针、函数指针用法很像。
如果经常重复使用同一个类型的block,你可以定义自己的block类型,例如
//返回值类型:float 两个参数类型:float 变量名称:MyBlockType typedef float (^MyBlockType)(float, float); MyBlockType myFirstBlock = // ... ; MyBlockType mySecondBlock = // ... ;
^表示block的开始,接一个返回值类型(可选,默认不写)再接一个(参数列表),后边接一个{代码块};例如
int multiplier = 7; int (^myBlock)(int) = ^(int num) { return num * multiplier; };
解读如下图所示
如果没有明确声明返回值类型,则根据block代码内容推断具体类型,例如
int multiplier = 7; //未指明返回值类型 int (^myBlock)(int) = ^(int num) { return num * multiplier; }; //指明返回值类型 int (^myBlock)(int) = ^ int (int num) { return multiplier; };
首先可以像函数一样引用三种标准类型的变量:
1、全局变量,包括静态局部变量
2、全局函数
3、作用域内的局部变量和参数
其次Blocks还支持另外两种类型:
4、__block修饰的变量
5、const导入的
所以Blocks内部处理变量的五种情况:
1、全局变量(包括作用域内的静态局部变量)可以直接访问
2、Blocks的参数可以直接访问
3、作用域内非静态局部变量,作为const变量引用(只读),强行修改编译器报错
4、被__block修饰的作用域内非静态局部变量,可以直接访问
5、Blocks内部声明的局部变量,可以直接访问
示例代码如下
int global_var = 1; - (void)viewDidLoad { [super viewDidLoad]; static int static_var = 2; __block int loacal_var = 3; __block int const_var = 3; void (^myBlock)(int) = ^(int number) { global_var = global_var * 10; static_var = static_var * 10; loacal_var = loacal_var * 10; number = number * 10; NSLog(@"局部变量:%d ,说明局部变量可以读取",const_var); NSLog(@"参数变量原:4 修改后%d,说明可以正常访问",number); }; myBlock(4); NSLog(@"全局变量原:1 修改后:%d,说明可以正常访问 ",global_var); NSLog(@"静态变量原:2 修改后:%d,说明可以正常访问 ",static_var); NSLog(@"__block修饰的局部变量原:3 修改后:%d,说明可以正常访问 ",loacal_var); } //输出结果 局部变量:3 ,说明局部变量可以读取 参数变量原:4 修改后40,说明可以正常访问 全局变量原:1 修改后:10,说明可以正常访问 静态变量原:2 修改后:20,说明可以正常访问 __block修饰的局部变量原:3 修改后:30,说明可以正常访问
对象通过Properties来引用Blocks。语法和定义Blocks语法类似;例如
@interface XYZObject : NSObject @property (copy) void (^blockProperty)(void); @end self.blockProperty = ^{ ... }; self.blockProperty();
注意:此处应该用copy,因为block需要被copy来保存引用的外界的变量,无需关心引用计数问题,编译器自动进行管理。
还可以通过自定义类型简化
typedef void (^XYZSimpleBlock)(void); @interface XYZObject : NSObject @property (copy) XYZSimpleBlock blockProperty; @end
当变量变成实例变量的时候,需要避免循环引用的问题。由于Blocks会持有使用的变量,比如在Viewcontroller中,block作为一个property被self持有,block中使用self调用方法,这样便造成循环引用的问题使得二者在不使用时均得不到释放。解决方法是在block中使用self的弱引用,用__weak修饰。
@interface XYZBlockKeeper : NSObject @property (copy) void (^block)(void); @end - (void)configureBlock { XYZBlockKeeper * __weak weakSelf = self; self.block = ^{ [weakSelf doSomething]; // capture the weak reference // to avoid the reference cycle } }
如果声明Block作为一个变量,可以像函数一样调用。例如
int (^oneFrom)(int) = ^(int anInt) { return anInt - 1; }; printf("1 from 10 is %d", oneFrom(10)); // Prints "1 from 10 is 9" float (^distanceTraveled)(float, float, float) = ^(float startingSpeed, float acceleration, float time) { float distance = (startingSpeed * time) + (0.5 * acceleration * time * time); return distance; }; float howFar = distanceTraveled(0.0, 9.8, 1.0); // howFar = 4.9
可以像传递其它参数一样传递block。大多数的情况不需要声明,只需要作为参数以内联的方式实现。例如
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; //此函数表示:以参数3的长度计算前2个参数的差值 return strncmp(left, right, 1); }); // Block implementation ends at "}" // myCharacters is now { "Charles Condomine", "George", "TomJohn" }
Cocoa Touch 提供了很多方法使用Blocks。可以传递block作为参数使用。例如
//是否包含指定字符串 __block BOOL found = NO; NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil]; NSString *string = @"gamma"; [aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) { *stop = YES; found = YES; } }]; // At this point, found == YES
很大程度上起到了简化的作用。
Blocks可以用处替换传统回调的原因如下:
1、方法调用和回调代码集中在一起;还常作为framework methods的参数。
2、可以直接访问局部变量。
对一个数组进行枚举,常用的方法有3种
1、for循环
2、for的泛型遍历for (type *object in collection)
3、使用带有block的简化方法。例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;
NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"]; //for循环 for (int i = 0; i < array.count; i ++) { NSString *item = array[i]; NSLog(@"index:%d value:%@",i,item); } //泛型遍历 for (NSString *item in array) { NSLog(@"value:%@",item); } //枚举方法 [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSLog(@"index:%ld value:%@",idx,obj); }];
每个block都是独立的工作单元,包含可执行代码和保存使用的数据,这使得它能够在多线程开发中被异步调用。
系统提供了一系列的多线程编程技术,其中包括两种任务调度机制:Operation queues 和 Grand Central Dispatch(GCD)。不像Thread关注怎么样管理运行,把需要执行的任务打包到blocks中,然添加到队列中,然后交给系统根据当时的资源自动执行。
//定义任务 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ ... }]; // 主线程执行 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; [mainQueue addOperation:operation]; // 后台执行 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:operation];
//获取并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //定义任务添加到队列并执行 dispatch_async(queue, ^{ NSLog(@"Block for asynchronous execution"); });