• Objective-C blocks 概要


    1.block的使用

    1.1什么是block?

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

    “带有自动变量”在Blocks中表现为“截取自动变量"
    “匿名函数”就是“不带名称的函数”

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

    • block的声明
    // 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • block的定义
        // ^返回值类型(参数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;
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • block的调用
        // 1.无返回值,无参数
        block1();
        // 2.有返回值,有参数
        int a = block5(2)
    • 1
    • 2
    • 3
    • 4

    2.block的底层数据结构

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

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

    //main.m
    #import <Foundation/Foundation.h>
     
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void(^block)(void) = ^{
                NSLog(@"我是 block");
            };
            block();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    转化为C++代码

    //参数结构体
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    3.block的变量捕获机制

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

    3.1 auto自动变量

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

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void(^block)(void) = ^{
                NSLog(@"%d",age);
            };
            block();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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);
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看出:

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

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

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

    3.2static类型的局部变量

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

        static int age = 10;
        void(^block)(void) = ^{
            NSLog(@"%d",age);
        };
        age = 20;
        block();
        // 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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));
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看出:

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

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

    3.3全局变量

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

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以看出:

    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++ 代码,并贴出部分变动代码

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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*/);}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    可以看出:

    • 编译器会将_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;

    在这里插入图片描述

  • 相关阅读:
    lock_icon_container LockIconContainer的显示
    c# 画球
    机器学习算法-集成学习
    集群模式执行Spark程序(第七弹)
    五种I/O模型
    自主实现HTTP服务器项目逻辑
    Spring系列九:Spring 事务
    开源库源码分析:Okhttp源码分析(一)
    Seata的4种模式
    CSS笔记-狂神
  • 原文地址:https://blog.csdn.net/m0_73974056/article/details/136490002