一、block的基本语法与使用
概述
Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调。
block的申明,跟C语言中的函数申明类似
void (^blockName) (int parameter);
//void代表返回值类型,后面一次是block名,参数
block的赋值,跟C语言中函数的定义类似
blockName = ^(int a){ //这里参数要和定义的参数类型一致
NSLog(@"parameter=%d",a);
};
block的调用
blockName(1); //输出 ‘parameter=1’
使用typedef定义Block类型
// 定义一种无返回值无参数列表的Block类型
typedef void(^SayHello)();
// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
SayHello hello = ^(){
NSLog(@"hello");
};
// 调用后控制台输出"hello"
hello();
block作为函数的参数
// 1.使用typedef定义Block类型
typedef int(^MyBlock)(int, int);
// 2.定义一个形参为Block的OC函数
- (void)useBlockForOC:(MyBlock)aBlock
{
NSLog(@"result = %d", aBlock(300,200));
}
// 3.声明并赋值定义一个Block变量
MyBlock addBlock = ^(int x, int y){
return x+y;
};
// 4.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock];
// 将第3点和第4点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
在block中访问局部变量的时候注意
在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之前的旧值
// 声明局部变量a
int a = 100;
void(^myBlock)() = ^{
NSLog(@"a = %d", a);
};
a = 101;
// 调用后控制台输出"a = 100"
myBlock();
在block中不可以修改局部变量
原因后面再详细介绍
// 声明局部变量global
int a = 100;
void(^myBlock)() = ^{
a ++; // 这句报错
NSLog(@"a = %d", a);
};
// 调用后控制台输出"a = 100"
myBlock();
block中可以访问全局变量
int global = 100; // 声明全局变量global
void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 调用后控制台输出"global = 101"
myBlock();
在实际应用中,从一个页面传值到另一个页面的情况比较常见,就简单介绍一下:
一、在第二个视图控制器的.h文件中定义声明Block属性
typedef void (^ReturnTextBlock)(NSString *showText); //申明一个block并重新定义一个名字
@interface TextFieldViewController : UIViewController
@property (nonatomic, copy) ReturnTextBlock returnTextBlock; //定义一个block属性
@end
二、实现第二个视图控制器的方法
//在需要的方法中调用
if (self.returnTextBlock != nil) {
self.returnTextBlock(self.inputTF.text); //block的调用
}
三、在第一个视图中获得第二个视图控制器,并且用第二个视图控制器来调用定义的属性
//在控制跳转到第二个页面的地方调用
TextFieldViewController *tfVC = [[TextFieldViewController alloc]init];
[tfVC returnText:^(NSString *showText) {
self.showLabel.text = showText;
}]; //block的定义
[self.navigationController pushViewController:tfVC animated:YES];
可能刚学iOS的同学会有点懵,把这里的逻辑梳理一下。首先在第二个页面定义了一个block变量,然后在需要的地方(比如点击返回第一个页面)调用了这个block,把UITextField中的内容传入了进去,但是block的定义是在第一个页面定义的,并且这里定义的内容是把传过来的内容(也就是第二个页面UITextField的内容)显示在label上面。
二、block的深入理解
块本身其实就是一个对象,在存放块对象的内存区域中,首个变量是指向Class对象的指针(isa指针)
首先要说明一下,block在创建的时候默认是创建在栈中的,这时候不能保证block的生存周期,所以我们在声明一个block属性的时候往往是用copy关键字,使用copy后会吧block拷贝在堆内存中,这时候才能保证block的生存周期。
现在我们来看看block的底层实现
- 常规block访问局部变量的情况
// OC代码如下
void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
// 转为C++代码如下
void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, global));
// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体的第三个元素是局部变量global的值
void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, global);
// 我们看一下结构体__main_block_impl_0的代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int global;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// 在OC中调用Block的方法转为C++代码如下,实际上是指向结构体的指针myBlock访问其FuncPtr元素,在定义Block时为FuncPtr元素传进去的__main_block_func_0方法
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
// __main_block_func_0方法代码如下,由此可见NSLog的global正是定义Block时为结构体传进去的局部变量global的值
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int global = __cself->global; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6y_vkd9wnv13pz6lc_h8phss0jw0000gn_T_main_d5d9eb_mi_0, global);
}
这是block访问局部变量的时候,由此可知,在Block定义时便是将局部变量的值传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值,同时内部的值也是不可修改的。
- 在局部变量前使用下划线下划线block修饰
//oc代码还是不变,只是将global前加一个__block关键字
// 转为C++代码如下
void(*myBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_global_0 *)&global, 570425344));
// 将变量类型精简之后C++代码如下,我们发现Block变量实际上就是一个指向结构体__main_block_impl_0的指针,而结构体的第三个元素是局部变量global的指针
void(*myBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &global, 570425344);
由此可知,在局部变量前使用__block修饰,在Block定义时便是将局部变量的指针传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响Block内部的值,同时内部的值也是可以修改的。