iOS block的使用与深入理解

一、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内部的值,同时内部的值也是可以修改的。

你可能感兴趣的:(iOS block的使用与深入理解)