iOS开发---Block详解

iOS开发—Block详解

Block的基础

什么是Blocks?

  • 用一句话来描述:带有自动变量的匿名函数(是不是一脸懵逼,不要担心,整篇博客都会围绕这句话展开)顾名思义:Block没有函数名,另外Block带有"^"标记,插入记号便于查找到Block
  • Blocks 也被称作闭包代码块。展开来讲,Blocks就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。
  • Block 共享局部作用域的数据。Block 的这项特征非常有用,因为如果您实现一个方法,并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及函数和全局变量(包括实例变量)。这种访问是只读的,但如果使用 __block 修饰符声明变量,则可在 Block 内更改其值。即使包含有块的方法或函数已返回,并且其局部作用范围已销毁,但是只要存在对该块的引用,局部变量仍作为块对象的一部分继续存在。

Blocks的语法

  • Block的完整语法格式如下:
^ returnType (argument list) {
  expressions
}

用一张图来表示

Block语法

  • 也可以写省略格式的Block,比如省略返回值类型:
^ (int x) {
  return x;
}

Block省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,没有return语句就使用void类型。

  • 如果没有参数列表,也可以省略参数列表:
^ {
  NSLog(@"hello world");
}
  • 与c语言的区别
    1. 没有函数名
    2. 带有"^"符号

Block类型变量

  • Block类型变量与一般的C语言变量完全相同,可以作为自动变量,函数参数,静态变量,全局变量使用
  • C语言函数将是如何将所定义的函数的地址赋值给函数指针类型变量中
int func (int count)
{
    return count + 1;
}

int (*funcptr) (int) = &func;
  • 使用Block语法就相当于生成了可赋值给Block类型变量的值。
//Blocks 变量声明与赋值
int (^blk) (int) = ^int (int count) {
    return count + 1;
};
//把Block语法生成的值赋值给Block类型变量
int (^myBlock)(int) = blk; 

与前面的使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型变量的"*"变为 “^”

  • 在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。
int (^func()(int)) {
    return ^(int count){
        return count + 1;
  	}
}

Block在oc中的使用

  • 通过property声明成员变量:@property (nonatomic, copy) 返回值类型 (^变量名) (参数列表);
@property (nonatomic, copy) void (^callBack) (NSString *);

- (void)useBlock {
  self.callBack = ^ (NSString *str){
    NSLog(@"useBlock %@", str);
  };
  self.callBack(@"hello world");
}
  • 作为方法参数:- (void)someMethodThatTaksesABlock:(返回值类型 (^)(参数列表)) 变量名;
- (void)callBackAsAParameter:(void (^)(NSString *print))callBack {
  callBack(@"i am alone");
}

//调用该函数
[self callbackAsAParameter:^(NSString *print) {
    NSLog(@"here is %@",print);
}];
  • 通过typedef定义变量类型
//typedef 返回值类型 (^声明名称)(参数列表);
//声明名称 变量名 = ^返回值类型(参数列表) { 表达式 };
typedef void (^callBlock)(NSSting *);

callBlock block = ^(NSString *str) {
  NSLog(@"%@", str);
}

与上一个知识点中指定Block为函数返回值对比

//原来的记述方式
void func(void (^blk)(NSString 8))
//用了 typedef 定义后的记述方式
void func(callBlock blk)

//原来的记述方式
void (^func()(NSString *)) 
//用了 typedef 定义后的记述方式
callBlock func()

Block截取变量

截获自动变量的值
  • 我们先看一个?
// 使用 Blocks 截获局部变量值
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    myLocalBlock();    // 打印结果:a = 10, b = 20

    a = 20;
    b = 30;

    myLocalBlock();    // 打印结果:a = 10, b = 20
}

为什么两次打印结果都是 a = 10, b = 20

明明在第一次调用 myLocalBlock();之后已经重新给变量 a、变量 b 赋值了,为什么第二次调用 myLocalBlock();的时候,使用的还是之前对应变量的值?

因为 Block 语法的表达式使用的是它之前声明的局部变量a、变量 bBlocks 中,Block 表达式截获所使用的局部变量的值,保存了该变量的瞬时值。所以在第二次执行 Block 表达式时,即使已经改变了局部变量 ab 的值,也不会影响 Block 表达式在执行时所保存的局部变量的瞬时值。

这就是 Blocks 变量截获局部变量值的特性。

通过__block说明符赋值
  • 上面我们想修改变量a,变量b的值,但是有没有成功,那么我们难道就没有方法来修改了么?别急,__block来也,只要用这个说明符修饰变量,就可以在块中修改。
// 使用 __block 说明符修饰,更改局部变量值
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;
    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;
        printf("a = %d, b = %d\n",a, b);  // 打印结果:a = 20, b = 30
    };
    
    myLocalBlock();
}

可以看到,使用__block说明符修饰之后,我们在 Block表达式中,成功的修改了局部变量值。

使用__block修饰符的自动变量被称为__blcok变量

Block的实现

在上一部分我们知道了Blocks带有局部变量的匿名函数。但是 Block 的实质究竟是什么呢?类型?变量?

要想了解 Block 的本质,就需要从 Block 对应的 C++ 源码来入手。

下面我们通过一步步的源码剖析来了解 Block 的本质。

Block的实质是什么?

准备工作
  1. 在项目中添加 blocks.m 文件,并写好 block 的相关代码。
  2. 打开『终端』,执行 cd XXX/XXX 命令,其中 XXX/XXXblock.m所在的目录。
  3. 继续执行clang -rewrite-objc block.m
  4. 执行完命令之后,block.m 所在目录下就会生成一个block.cpp文件,这就是我们需要的 block 相关的 C++源码。
Block源码预览
  • 转换前OC代码:
int main () {
    void (^myBlock)(void) = ^{
        printf("myBlock\n");
    };
    myBlock();
    return 0;
}
  • 转换后c++代码:
/* 包含 Block 实际函数指针的结构体 */
struct __block_impl {
    void *isa;
    int Flags;               
    int Reserved;       // 今后版本升级所需的区域大小
    void *FuncPtr;      // 函数指针
};

/* Block 结构体 */
struct __main_block_impl_0 {
    // impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体
    struct __block_impl impl;
    // Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

/* Block 主体部分结构体 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("myBlock\n");
}

/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升级所需区域大小
    size_t Block_size;    // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/* main 函数 */
int main () {
  	//myBlock的初始化
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  	//myBlock的调用
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

下面我们一步一步来拆解转换后的源码

Block结构体
  • 我们先来看看 __main_block_impl_0 结构体( Block 结构体)
/* Block 结构体 */
struct __main_block_impl_0 {
    // impl:Block 的实际函数指针,指向包含 Block 主体部分的 __main_block_func_0 结构体
    struct __block_impl impl;
    // Desc:Desc 指针,指向包含 Block 附加信息的 __main_block_desc_0() 结构体
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

从上边我们可以看出,__main_block_impl_0 结构体**(Block 结构体)**包含了三个部分:

  1. 成员变量 impl;
  2. 成员变量 Desc 指针;
  3. __main_block_impl_0 构造函数。
struct __block_impl 结构

我们先看看第一部分impl__block_impl结构体类型的成员变量

/* 包含 Block 实际函数指针的结构体 */
struct __block_impl {
    void *isa;          // 用于保存 Block 结构体的实例指针
    int Flags;          // 标志位
    int Reserved;       // 今后版本升级所需的区域大小
    void *FuncPtr;      // 函数指针
};
  • __block_impl包含了 Block 实际函数指针 FuncPtrFuncPtr指针指向 Block 的主体部分,也就是 Block 对应 OC 代码中的 ^{ printf("myBlock\n"); };部分。
  • 还包含了标志位 Flags,在实现block的内部操作时会用到
  • 今后版本升级所需的区域大小 Reserved
  • __block_impl结构体的实例指针 isa
static struct __main_block_desc_0结构

第二部分 Desc 是指向的是 __main_block_desc_0 类型的结构体的指针型成员变量,__main_block_desc_0 结构体用来描述该 Block 的相关附加信息:

/* Block 附加信息结构体:包含今后版本升级所需区域大小,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升级所需区域大小
    size_t Block_size;  // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0 构造函数

第三部分是 __main_block_impl_0 结构体(Block 结构体) 的构造函数,负责初始化 __main_block_impl_0 结构体(Block 结构体) 的成员变量。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
}
  • 关于结构体构造函数中对各个成员变量的赋值,我们需要先来看看 main()函数中,对该构造函数的调用。
  void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  • 我们可以把上面的代码稍微转换一下,去掉不同类型之间的转换,使之简洁一点:
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;
  • 这样,就容易看懂了。该代码将通过 __main_block_impl_0构造函数,生成的 __main_block_impl_0结构体(Block 结构体)类型实例的指针,赋值给 __main_block_impl_0结构体(Block 结构体)类型的指针变量 myBlock

可以看到, 调用 __main_block_impl_0构造函数的时候,传入了两个参数。

  1. 第一个参数:__main_block_func_0

    • 其实就是 Block 对应的主体部分,可以看到下面关于 __main_block_func_0结构体的定义 ,和OC 代码中 ^{ printf("myBlock\n"); };部分具有相同的表达式。
    • 这里参数中的 __cself是指向 Block 的值的指针变量,相当于 OC 中的 self
    /* Block 主体部分结构体 */
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("myBlock\n");
    }
    
  2. 第二个参数:__main_block_desc_0_DATA__main_block_desc_0_DATA包含该 Block 的相关信息。
    我们再来结合之前的 __main_block_impl_0结构体定义。

    • __main_block_impl_0结构体(Block 结构体)可以表述为:
    struct __main_block_impl_0 {
        void *isa;          // 用于保存 Block 结构体的实例指针
        int Flags;          // 标志位
        int Reserved;       // 今后版本升级所需的区域大小
        void *FuncPtr;      // 函数指针
        struct __main_block_desc_0* Desc;      	// Desc:Desc 指针
    };
    
    • __main_block_impl_0构造函数可以表述为:
    impl.isa = &_NSConcreteStackBlock;     // isa 保存 Block 结构体实例
    impl.Flags = 0;        // 标志位赋值
    impl.FuncPtr = __main_block_func_0;    // FuncPtr 保存 Block 结构体的主体部分
    Desc = &__main_block_desc_0_DATA;      // Desc 保存 Block 结构体的附加信息
    
调用

在分析了Block结构体和成员变量,现在我们看看main函数中是如何调用block的

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
  • myBlock结构体的第一个成员变量为__block_impl,所以
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值