Objective-C blocks 概要

1.block的使用

1.1什么是block?

Blocks是C语言的扩充功能:带有自动变量(局部变量)的匿名函数。

"带有自动变量"在Blocks中表现为"截取自动变量"

"匿名函数"就是"不带名称的函数"

块,封装了函数调用及调用环境的OC对象

  • block的声明
objectivec 复制代码
// 1.
@property (nonatomic, copy) void(^myBlock1)(void);
// 2.BlockType:类型别名
typedef void(^BlockType)(void);
@property (nonatomic, copy) BlockType myBlock2;
// 3.
// 返回值类型(^block变量名)(参数1类型,参数2类型,...)
void(^block)(void);
  • block的定义
objectivec 复制代码
    // ^返回值类型(参数1,参数2,...){};
    // 1.无返回值,无参数
    void(^block1)(void) = ^{
        
    };
    // 2.无返回值,有参数
    void(^block2)(int) = ^(int a){
        
    };
    // 3.有返回值,无参数(不管有没有返回值,定义的返回值类型都可以省略)
    int(^block3)(void) = ^int{
        return 3;
    };
    // 以上Block的定义也可以这样写:
    int(^block4)(void) = ^{
        return 3;
    };
    // 4.有返回值,有参数
    int(^block5)(int) = ^int(int a){
        return 3 * a;
    };
  • block的调用
objectivec 复制代码
    // 1.无返回值,无参数
    block1();
    // 2.有返回值,有参数
    int a = block5(2);

2.block的底层数据结构

通过Clang将以下的OC代码转化为C++代码

// Clang

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

objectivec 复制代码
//main.m
#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"我是 block");
        };
        block();
    }
    return 0;
}

转化为C++代码

objectivec 复制代码
//参数结构体
struct __main_block_impl_0 {
  struct __block_impl impl;// block 结构体
  struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数
 ** 返回值:__main_block_impl_0 结构体
 ** 参数一:__main_block_func_0 结构体
 ** 参数二:__main_block_desc_0 结构体的地址
 ** 参数三:flags 标识位
*/
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
 
//block 结构体
struct __block_impl {
  void *isa;//block 的类型
  int Flags;
  int Reserved;
  void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};
 
//封装了 block 中的代码
//参数是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
        }
 
 
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)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
         ** void(^block)(void) = ^{
                NSLog(@"调用了block");
            };
         ** 定义block的本质:
         ** 调用__main_block_impl_0()构造函数
         ** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
         ** __main_block_func_0 封装了block里的代码
         ** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
         ** 把这个地址赋值给 block
         */
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
         ** block();
         ** 调用block的本质:
         ** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
         */    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

3.block的变量捕获机制

  • 对于全局变量,不会捕获到block内部,访问方为直接访问
  • 对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为值传递
  • 对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为指针传递
  • 对于对象类型的局部变量,block会连同修饰符一起捕获

3.1 auto自动变量

将以下 OC 代码转换为 C++ 代码,并贴出部分变动代码

objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(void) = ^{
            NSLog(@"%d",age);
        };
        block();
    }
    return 0;
}
objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;//生成的变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_40c716_mi_0,age);
       }

可以看出:

  • 在__main_block_impl_0结构体中会自动生成一个相同类型的age变量
  • 构造函数__main_block_impl_0中多出了一个age参数,用来捕获外部的变量

由于传递方式为值传递,所以我们在block外部修饰age变量时,不会影响到block中的age变量

总的来说,所谓"截获自动变量"意味着在执行Block语法时,Block语法表达式所用的自动变量被保存到Block的结构体实例中(即Block自身)中

3.2static类型的局部变量

将以下OC代码转化为C++代码,并贴出部分变动代码

objectivec 复制代码
    static int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    age = 20;
    block();
    // 20
objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *age = __cself->age; // bound by copy
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_cb7943_mi_0,(*age));
        }

可以看出:

  • __main_block_impl_0结构体中生成了一个相同类型的age变量
  • __main_block_impl_0构造函数多了个参数,用来捕获外部的age变量的地址

由于传递方式是指针传递,所以修改局部变量age时,age的值会随之变化

3.3全局变量

将以下OC代码转化为C++代码,并贴出部分变动代码

objectivec 复制代码
int height = 10;
static int age = 20;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"%d,%d",height,age);
        };
        block();
    }
    return 0;
}
objectivec 复制代码
int height = 10;
static int age = 20;
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_7a340f_mi_0,height,age);
        }

可以看出:

  1. __main_block_impl_0结构体中,并没有自动生成age和height全局变量,也就是说没有将变量捕获到block内部

虽然block语法的匿名函数部分简单地变换为了C语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。

但是静态变量的情况下,转换后的函数原本就设置在含有Block语法的函数外,所以无法从变量作用域访问

为什么局部变量需要捕获,而全局变量不用呢?

  • 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获
  • 局部变量,外部不能直接访问,所以需要捕获
  • auto类型的局部变量可能会销毁,其内存会消失,block将来执行代码的时候不可能再去访问呢块内存,所以捕获其值
  • static变量会一直保存在内存中,所以捕获其地址即可

3.4 _block修饰的变量

3.4.1 _block的使用

默认情况下block是不能修改外面的auto变量,解决办法?

  • 变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址
  • 全局变量
  • _block(我们只是希望临时用一下这个变量临时改一下而已,而改为static变量和全局变量会一直在内存中)
3.4.2 _block修饰符
  • _block同于解决block内部无法修改auto变量值的问题;
  • _block不能修饰全局变量,静态变量;
  • 编译器会将_block变量包装成一个对象(struct __Block_byref_age_0byref:按地址传递));
  • 加_block修饰不会改变变量的性质,他还是auto变量;
  • 一般情况,对捕获变量进行赋值(赋值!=使用)操作需要添加_block修饰符,比如给数组添加或删除对象,就不用加_bolck修饰符;
  • 在 MRC 下使用 __block 修饰对象类型,在 block 内部不会对该对象进行 retain 操作,所以在 MRC 环境下可以通过 __block 解决循环引用的问题;

将以下 OC 代码转换为 C++ 代码,并贴出部分变动代码

objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        void(^block)(void) = ^{
            age = 20;
            NSLog(@"block-%d",age);
        };
        block();
        NSLog(@"%d",age);
    }
    return 0;
}
objectivec 复制代码
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//持有指向该实例自身的指针
 int __flags;
 int __size;
 int age;
};
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
 
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_75529b_mi_0,(age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

可以看出:

  • 编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象
  • 以上age = 20 的赋值过程:通过block的__main_block_func_0结构体实例中( __Block_byref_age_0)类型的age指针,找到 __Block_byref_age_0结构体的对象, __Block_byref_age_0结构体对象持有指向实例本身的__forwarding指针,通过成员变量_forwarding访问 __Block_byref_age_0结构体里的age变量,并将值改为20;
相关推荐
爱吃生蚝的于勒几秒前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
binishuaio10 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE12 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻16 分钟前
WPF中的依赖属性
开发语言·wpf
洋24024 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙26 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点27 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder44 分钟前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
苏三有春44 分钟前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python