Blocks

作者: helinyu | 来源:发表于2021-08-30 21:16 被阅读0次

    2.1 blocks 概要

    什么是blocks?
    带有自动变量(局部变量)的匿名函数 [函数指针]
    可分: (1)带有自动变量 (2)匿名函数

    C 语言标准不允许存在这样不含有名称的函数。

    C语言的函数中可能使用的变量;

    1. 自动变量(局部变量)
    2. 函数的参数
    3. 静态变量(静态局部变量)
    4. 静态全局变量
    5. 全局变量

    其中,在函数的多次调用之间能够传递值的变量有;
    (1)静态变量(静态局部变量) (2)静态全局变量 (3)全局变量
    C++ 中的静态变量只可以复制一次的实现原理

    blocks 提供了类似由C++和OC类生成实例或对象来保持变量值的方法,其代码量与编写C语言函数差不多。 block 保存自动变量值。

    2.2 blocks模式

    2.2.1 block 语法
     ^(int event) { // 省略
            printf("button id event:%d",event);
        }
        
        ^ void(int event) { // 全写法
            printf("button id event:%d",event);
        }
    

    和C语言函数相比,只有两点不同

    (1)没有函数名 【因为它是那你明天函数】
    (2) 带有 ^ 【返回值类型带有 ^ 记号】




    省略的样式
    省略的样式
    2.2.2 block 类型变量
    int func(int count) {
        return count + 1;
    }
    int (*funcptr)(int) = &func;
    

    c语言函数中,可以将锁定义函数的地址赋值给函数指针类型变量中。 【所以,这里就是讲函数func的地址赋值给之后怎变量funcptr】


    block类比

    由于block方式极为复杂, 可以和函数指针类型一样,使用typedef来解决问题。 typedef 让记述方式更加简单
    typedef int(^Blk)(int); 这样 就声明了一个Blk类型变量。

    // 原来的记述方式
    void func(int (^blk)(int))
    
    // 现在的方式
    void func(Blk blk) 
    

    将赋值 给Block类型变量中的block方法像C语言通常的函数调用那样使用。这种方法与使用函数指针累心变量调用函数的方法几乎完全相同。

    函数指针类型变量:
    int result = (*funcptr)(10);
    
    // 调用block类型变量:
    int result = blk(10);
    
    //PS: 可以看到它们的调用没有什么区别。 
    
    // block 作为参数和其他变量作为参数没有什么区别。 
    int func(Blk blk, int rate) {return blk(rate); }
    - (int)methodUsingBlock:(Blk)blk rate:(int)rate {return blk(rate);}
    

    PS : block 完全可以和其他变量一样进行使用。

    2.2.3 截获自动变量值

    上面我们已经理解了“带有自动变量值的匿名函数” 中的匿名函数。
    而“带有自动变量值” 是什么呢? 在blocks中表现为截获自动变量值。

        {
            int dmy = 256;
            int val  = 10;
            const char *fmt = "val = %d \n";
            void (^blk)(void) = ^{
                printf(fmt, val);
            };
            val = 2;
            fmt = "These values were changed. val= %d \n";
            blk();
            return ;
        }
    结果:val= 10;  
    

    也就是里面使用的是瞬间的值。即为捕获的是外面变量的值的瞬间值。

    2.2.4 __block 说明符

    获取自动变量值,即为在block里面是不可以修改外面的自动变量的值的。 只是拿来在block进行读取使用而已。

    想要给外面的自动变量进行赋值的时候,需要使用__block 来进行修饰这个变量。

      {
            int dmy = 256;
            __block int val  = 10;
            const char *fmt = "val = %d \n";
            void (^blk)(void) = ^{
                printf(fmt, val);
            };
            val = 2;
            fmt = "These values were changed. val= %d \n";
            blk();
            return;
        }
    使用了__block 修饰符之后。 
    结果: val = 2 
    

    PS: 使用附有__block 说明符的自动变量可在block中赋值,该变量称为 __block 变量。

    2.2.5 截获的自动变量

    直接给截获的自动变量赋值,将会报错。
    如果截获OC对象,调用变更该对象的方法会产生编译错误吗?

       NSMutableArray *array = [NSMutableArray array];
    //没有报错
            void (^blk)(void) =^(void) {
                id obj = [NSObject new];
                [array addObject:obj];
            };
            
    // 报错
            void (^blk1)(void) = ^(void) { 
                array = [NSMutableArray new];
            };
    

    调用array的有关方法是没有编译错误的,而直接给array赋值就会产生错误的。 该源代码中截获的变量值为NSMutableArray类的的对象。 如果用C语言来描述,即为截获NSMutableArray类对象用的结构体实例指针。 虽然赋值给截获的自动变量array的操作会产生编译错误,但是使用截获的值却不会有任何问题。 如果要进行赋值,还是要使用__block 进行修饰。

            const char text[] ="hello";
            void (^blk)(void) = ^(void) {
                printf("%c \n", text[2]);
            };
    

    使用C语言数组时候必须要小心使用其指针。
    上面只是使用C语言的字符串字面量数组, 而并没有向截获自动变量赋值,因此看似灭有问题。实际会产生编译错误。
    AN : 因为现在blocks中,截获自动变量的方法并没有实现对C语言数据的截获。 这个时候就需要使用只恨来解决这个问题。

            const char *text ="hello";
            void (^blk)(void) = ^(void) {
                printf("%c \n", text[2]);
            };
    

    2.3 Blocks 的实现

    clang -rewrite-objc 源代码文件名 将OC的源码编译成为C++的源码查看。

    // 一个简单使用block的代码
    - (void)initTest {
        void (^blk)(void) = ^(void){
            printf("block \n");
        };
        blk();
        return;
    }
    
    // 上面的方法clang之后变成了C语言的代码
    
    struct __block_impl {  //block实现的内部结构
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;// 用于保存函数地址
    };
    
    
    // block对象的结构
    struct __XNBlockModel__initTest_block_impl_0 {
        struct __block_impl impl; // 声明了这样的实现变量 , 看上面的结构
        struct __XNBlockModel__initTest_block_desc_0* Desc; //block的大小
    
        // 给imp赋值有关的值
        // 给Desc赋值有感的值
        __XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_block_desc_0 *desc, int flags=0) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp; // 调用的函数
            Desc = desc; // 描述内容
        }
    };
    
    // block里面的内容
    static void __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
        printf("block \n");
    }
    
    // 这个只是表示大小
    static struct __XNBlockModel__initTest_block_desc_0 {
        size_t reserved;
        size_t Block_size; // block大小
    } __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)}; // 这里初始化了desc内容
    
    // initTest方法
    static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
        void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA));
      // void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA)
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        // 简化:(*blk ->FuncPtr)(blk)
    
        return;
    }
    // 该函数里面的__cself 相当于C++ 中的this 。 即参数__cself 为指向block值的变量。 
    // 上面代码描述: __XNBlockModel__initTest_block_impl_0 结构体类型的自动变量【即栈上生成对应结构体实例指针】, 赋值给__XNBlockModel__initTest_block_impl_0 结构体指针类型的白能量blk。 
    //(*blk ->FuncPtr)(blk)  函数指针调用函数。 
    

    isa =&_NSConcreteStackBlock 是什么呢?
    这个内容就是,我们获取_NSConcreteStackBlock 类对象的地址赋值给了当前block对象的isa, 说明isa的有关内容可以在_NSConcreteStackBlock , 可以说:
    _NSConcreteStackBlock 是__XNBlockModel__initTest_block_impl_0 的元类。
    即为:在将block作为OC的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。

    2.3.2 截获自动变量值
    // OC  中的代码
    - (void)initTest {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d \n";
        void (^blk)(void) = ^(void) {
            printf(fmt, val);
        };
        val = 2;
        fmt = "These values were changed. val = %d \n";
        blk();
        return;
    }
    
    clang 转成C代码之后
    // 和上面的区别是:多了fmt 、val
    // Note: Block 语法表达式中没有使用的自动变量不会被追加, eg:dmy
    // Block的自动变量截获只针对Block中使用的自动变量。
    struct __XNBlockModel__initTest_block_impl_0 {
      struct __block_impl impl;
      struct __XNBlockModel__initTest_block_desc_0* Desc;
      const char *fmt;
      int val;
    //    构造函数, 多了fmt 和val的初始化
      __XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
      const char *fmt = __cself->fmt; // bound by copy (范围拷贝)
      int val = __cself->val; // bound by copy 
            printf(fmt, val); // 打印出来这个内容
        }
    
    static struct __XNBlockModel__initTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)};
    
    static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d \n";
        void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, fmt, val)); 
        // 看到这里,fmt, val是传值进去的
        // 简化:void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, fmt, val));
        val = 2;
        fmt = "These values were changed. val = %d \n";
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        return;
    }
    // Note: “截获自动变量值” : 在执行block语法时候,block语法表达式使用的自动变量值被保存到Block的结构体实例中。 
    

    C语言是不可以编译的
    C语言是不可以编译的,xcode上的报错
    2.3.3 __block 说明符

    block获取的是值, 即为值传递, 所以,即使可以在block中赋值给val这个,也是不会改变原来传递的值的。


    给val赋值,编译出现错误

    这样:我们就无法在Block中保存至了,极为不便。

    解决方案:
    1) C语言中有一个变量,允许block改写 值。
    静态变量、静态全局变量、全局变量

    block语法的匿名函数部分简单地变换为了C语言函数, 但从这个变换的函数中访问静态全局变量、全局变量并没有任何改变,可以直接使用。
    但是,静态变量情况下,转换后的函数原本就设置为含有block语法的函数外,所以无法从变量作用域访问。

    // OC  源码
    int global_val = 1;
    static int static_global_val = 2;
    
    - (void)initTest {
        static int static_val = 3;
        void (^blk)(void) = ^(void) {
            global_val *=1;
            static_global_val *=2;
            static_val *=3;
        };
        blk();
    }
    
    // 静态变量转化为C语言之后
    int global_val = 1;
    static int static_global_val = 2;
    
    struct __XNBlockModel__initTest_block_impl_0 {
      struct __block_impl impl;
      struct __XNBlockModel__initTest_block_desc_0* Desc;
      int *static_val; // 静态变量
      __XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
      int *static_val = __cself->static_val; // bound by copy
    
            global_val *=1;
            static_global_val *=2;
            (*static_val) *=3; // 静态变量的访问
        }
    
    static struct __XNBlockModel__initTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)};
    
    static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
        static int static_val = 3;
        void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, &static_val));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    
    没有使用静态变量的原因

    2) 方法2 使用__block 说明符。更加准确说法:__block 存储域类说明符
    C语言有存储域类说明符: typedef ,extern, static, auto, register

    __block 类似 static, auto, register , 用于指定将变量值设置到哪个存储域中。

    // OC源代码
    - (void)initTest {
        __block int val = 10;
        void (^blk)(void) = ^(void) {
            val = 1;
        };
        blk();
    }
    
    转成为C的源码:
    struct __Block_byref_val_0 {
        void *__isa;
        __Block_byref_val_0 *__forwarding;
        int __flags;
        int __size;
        int val;
    };
    
    struct __XNBlockModel__initTest_block_impl_0 {
        struct __block_impl impl;
        struct __XNBlockModel__initTest_block_desc_0* Desc;
        __Block_byref_val_0 *val; // by ref  持有__Block_byref_val_0 类型的实例变量
        __XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    static void __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
        __Block_byref_val_0 *val = __cself->val; // bound by ref
        (val->__forwarding->val) = 1;
    }
    
    //  比原来多了下面连个方法,copy0 , dispose_0
    static void __XNBlockModel__initTest_block_copy_0(struct __XNBlockModel__initTest_block_impl_0*dst, struct __XNBlockModel__initTest_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __XNBlockModel__initTest_block_dispose_0(struct __XNBlockModel__initTest_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __XNBlockModel__initTest_block_desc_0 {
        size_t reserved;
        size_t Block_size;
        // 多了这两个函数指针
        void (*copy)(struct __XNBlockModel__initTest_block_impl_0*, struct __XNBlockModel__initTest_block_impl_0*);
        void (*dispose)(struct __XNBlockModel__initTest_block_impl_0*);
    
    } __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0), __XNBlockModel__initTest_block_copy_0, __XNBlockModel__initTest_block_dispose_0};
    
    static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        // __Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
    
        void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        //     void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, &val, 570425344));
    
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        // (*blk -> FuncPtr)(blk)
    }
    

    // Note
    // (1)__block 变量也同Block一样变成了__Block_byref_val_0 结构体类型的自动变量。【即 栈上生成的__Block_byref_val_0结构体实例】 ,该比那拉给你初始化为0, 切这个值也出现在结构体实例化的初始化中。 这以为这该结构体持有原自动变量


    为什么使用一个__forwarding成员变量

    下面主要说明:

    1)Block超出变量作用域可存在的理由
    2)__block 变量的结构体成员变量__forwarding存在的理由。

    2.3.4 Block 存储域










    forward的原因

    2.3.5 __block 变量存储域

    【需要补充】

    block上面的内容, 这个需要进一步去理解。 block等等内容。

    相关文章

      网友评论

          本文标题:Blocks

          本文链接:https://www.haomeiwen.com/subject/aeooiltx.html