Blocks

2021-08-30  本文已影响0人  helinyu

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等等内容。

上一篇下一篇

猜你喜欢

热点阅读