• iOS——【Blocks】


    Blocks概要

    Blocks是C语言的扩充功能,即带有自动变量的匿名函数。匿名函数就是不带函数名的函数。这一概念同样被称为“闭包”,lambda计算等。
    自动变量是在函数内部声明的变量,其作用域仅限于声明它的函数内部。这意味着它们只能在其声明的函数内部使用,并且在函数执行完毕后会被自动销毁。

    Blocks模式

    block语法的完整形式:

    ^void (int event) {
        //...
    }
    
    • 1
    • 2
    • 3

    即为:

    ^返回值类型 参数列表 表达式
    与C语言不同的地方有:

    1. 没有“^”(插入记号):插入该记号便于查找。
    2. 没有函数名:因为为匿名函数。

    block的返回值类型是可以省略的,省略返回值类型后,如果有return语句就返回该返回值类型,没有的话就使用void类型。
    其次,如果不使用参数,参数列表也可以省略。

    Block类型变量

    同样的,在Block语法下,可将Block语法赋值给声明为Block类型的变量中。在有关Blocks的文档中,“Block”既指源代码中的Block语法,也指由Blcok语法所生成的值。
    声明Block类型变量的语法如下:

    int (^blk)(int);
    
    • 1

    该Block类型变量与一般的C语言变量完全相同,可以用于:自动变量、函数参数、静态变量、静态全局变量、全局变量。
    下面使用Block语法将Block赋值为Block变量:

    int (^blk)(int) = ^(int count)(return count+1);
    
    • 1

    由“^”开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所以也可以由Block类型变量向Block类型变量赋值。

    int (^blk1)(int) = blk;
    int (^blk2)(int);
    blk2 = blk1;
    
    • 1
    • 2
    • 3

    在函数参数中使用Block类型变量可以向函数传递Block:

    void func (int (^blk)(int)) {
      
    }
    
    • 1
    • 2
    • 3

    在函数返回值中指定Block类型,可以将Block作为返回值返回:

    int (^func()(int)) {
      return ^(int count)(return count+1);
    }
    
    • 1
    • 2
    • 3

    还可以使用typedef简化块的记述方式,见EOF学习的博客第38条。
    将赋值给Block类型变量的Block方法像C语言通常的函数调用那样使用,这种方法与使用函数指针类型变量调用函数的方法几乎完全相同。变量funcptr为函数指针类型的时候,像下面这样调用函数指针类型变量:

    int result = (*funcptr)(10);
    
    • 1

    变量blk为Block类型的情况下,这样调用Block类型变量:

    int result = blk(10);
    
    • 1

    通过Block类型变量调用Block与C语言通常的函数调用没有区别:

    // blk_t blk就是一个块对象
    int func(blk_t blk, int rate) {
      return blk(rate);
    }
    
    //块对象在OC中也可以当参数
    - (int) useBlock: (blk_t)blk rate:(int)rate;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    也可以使用指向Block类型变量的指针,即Block指针类型变量。

    typedef int (^blk_t)(int);
    blk_t blk = ^(int count)(return count+1);
    blk_t *blkptr = &blk;
    (*blktr)(10);
    
    • 1
    • 2
    • 3
    • 4

    截获自动变量值

    Blocks中,Blocks表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。
    因为Block表达式保存了自动变量的值,所以在执行Blocks语法后,即使改写Block中所使用的自动变量的值也不会影响执行时自动变量的值。

    int main() {
      int dmy = 256;
      int val = 10;
      const char *fmt = “val = %d\n”;
      //这里声明了块就是在截获变量,此时捕获的fmt和val的值就是在该块被创建之前那一瞬间的值,哪怕后面已经改变了fmt和val的值,这里截获的结果还是改变之前的值,因为那才是这个块创建那一瞬间的时候变量的值。
      void(^blk)(void) = ^{printf(fmt, val);};
      val = 2;
      fmt = “These value were changed. val = %d\n”;
      blk();
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    __block说明符

    实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能修改该值。如果尝试改写截获的自动变量的值:

    int val = 0;
    void (^blk)(void) = ^{val = 1};
    
    • 1
    • 2

    我们发现编译的时候会报错。
    若想在Block语法表达式中给自动变量赋值,需要在该自动变量上附加__block说明符:

    __block int val = 0;
    void (^blk)(void) = ^{val = 1};
    
    • 1
    • 2

    使用附有_ _blcok说明符的可在Block中赋值,该变量称为__block变量。

    截获的自动变量

    已知如果尝试改写截获的自动变量的值,编译的时候会报错。那么截获OC对象,调用变更该对象的方法也会产生编译错误吗?

    id array = [[NSMutableArray alloc] init];
    void (^blk)(void) = ^{
      id obj = [[NSObject alloc] init];
      [array addObject: obj];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样是没有问题的,而向截获的变量array赋值就会编译错误。
    虽然向截获的变量array赋值会产生错误,但是使用截获的值不会产生错误。
    这种情况下,需要给截获的自动变量附加 _block说明符。

    __block id array = [[NSMutableArray alloc] init];
    void (^blk) (void) = ^{
      array = [[NSMutableArray alloc] init];
    };
    
    • 1
    • 2
    • 3
    • 4

    Blocks的实现

    Block的实质

    clang (LLVM 编译器)具有转换为我们可读源代码的功能 。通过“-rewrite-objo”选项就能将含有Block语法的
    源代码变换为C ++的源代码。说是C ++,其实也仅是使用 了str uc t 结构,其本质是C 语言源代码。

    clang -rewrite-objc 源代码文件名
    
    • 1

    我们转换如下的block代码:

    int main() {
      void (^blk)(void) = ^{printf("Block\n”);};
      blk(); 
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此代码的Block语法最为简单,它省略了返回值类型以及参数列表。该源代码通过clang可变换为以下形式:

    //经过clang转换后的C++代码
    struct __block_impl {
        void *isa; // 指向 block 的类的指针
        int Flags; // 标志位
        int Reserved; // 保留字段
        void *FuncPtr; // 指向 block 函数的指针
    };
    
    struct __main_block_impl_0 {
        struct __block_impl impl; // block 的实现
        struct __main_block_desc_0 *Desc; // block 的描述
        // 构造函数,初始化 impl 和 Desc 字段
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
            impl.isa = &_NSConcreteStackBlock; // 设置 isa 指针
            impl.Flags = flags; // 设置标志位
            impl.FuncPtr = fp; // 设置函数指针
            Desc = desc; // 设置描述指针
        }
    };
    
    // block 函数的实现
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Block\n"); // 打印信息
    }
    
    // block 的描述结构
    static struct __main_block_desc_0 {
        size_t reserved; // 保留字段
        size_t Block_size; // block 的大小
    } __main_block_desc_0_DATA = {
        0, // 保留字段为 0
        sizeof(struct __main_block_impl_0) // block 实现的大小
    };
    
    int main(int argc, const char * argv[]) {
        // 定义一个函数指针 blk,指向 __main_block_impl_0 结构体的实例,该实例通过 __main_block_impl_0 构造函数初始化
        void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    
        // 调用 block 函数指针
        ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
        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

    struct _ _block_impl:这个结构体定义了一个 block 的实现,包含了指向 block 类的指针 isa、标志位 Flags、保留字段 Reserved 和指向 block 函数的指针 FuncPtr。
    struct _ _main_block_impl_0:这个结构体扩展了 __block_impl,定义了一个特定的 block 实现。它包含一个 _ _block_impl 的实例、一个指向 block 描述的指针 Desc,并且有一个构造函数用于初始化这些字段。
    _ _main_block_impl_0(void *fp, struct _ _main_block_desc_0 *desc, int flags=0):这是 _ _main_block_impl_0 的构造函数,用于初始化 impl 和 Desc 字段。
    _ _main_block_func_0(struct _ main_block_impl_0 * _cself):这是 block 的实际函数实现,在本例中只是简单地打印 “Block” 信息。
    struct _ _main_block_desc_0:这个结构体描述了 block 的大小和保留空间。
    _ _main_block_desc_0_DATA:这是 block 描述的实际数据,包括大小信息。
    main 函数中,首先定义了一个函数指针 blk,它指向一个 _ _main_block_impl_0 结构体的实例,该实例通过 _ main_block_impl_0 的构造函数初始化,并且调用了这个 block 函数指针,打印 “Block” 信息。
    该函数的参数
    cself相当于C++实例方法中所指的自身变量this,或是OC实例方法中指向对象自身的变量self,即参数 _cself为指向Block值的变量。

    由这次Block语法变换而来的_main_block_func_0 函数并不使用__cself。我们先来看看该参数的声明:

    struct __main_block_impl_0* __cself
    
    //结构体声明:
    struct __main_block_impl_0 {
      	struct __block_impl impl;
      	struct __main_block_desc_0* Desc;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    第一个成员变量是impl,其__block_impl结构体的声明:

    struct __block_impl {
    	void *isa;
    	int Flags;
    	int Reserved;
    	void *FuncPtr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第二个成员变量是Desc指针,其__main_block_desc_0结构体的声明:

    static struct __main_block_desc_0 {
      	size_t reserved;
      	size_t Block_size;
    }
    
    • 1
    • 2
    • 3
    • 4

    以上就是初始化__main_block_impl_0结构体成员的源代码。我们刚刚跳过了_NSConcreteStackBlock的说明。_NSConcreteStackBlock用于初始化__block_impl结构体的isa成员。虽然大家很想了解它,但在进行讲解之前,我们先来看看该构造函数的调用。

    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    
    • 1

    因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下:

    struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
    struct __main_block_impl_0 *blk = &tmp;
    
    • 1
    • 2
    • 3

    这样就容易理解了。该源代码将_ main_block_impl0结构体类型的自动变量,即栈上生成的 main_block_impl_0 结构体实例的指针,赋值给 _main_block_impl_0结构体指针类型的变量 blk。以下为这部分代码对应的最初源代码。

    void(^blk)(void)=^{printf("Block\n");};
    
    • 1

    将 Block 语法生成的Block赋给Block 类型变量blk。它等同于将_ main_block_impl_0 结构体实例的指针赋给变量blk。该源代码中的 Block 就是 main_block_impl_0 结构体类型的自动变量,即栈上生成的 main_block_impl_0结构体实例。
    下面就来看看
    _main_block_impl_0结构体实例构造参数。

    __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    
    • 1

    第一个参数是由Block语法转换的C语言函数指针。第二个参数是作为静态全局变量初始化的_ main_block_desc_0 结构体实例指针。以下为 _main_block_desc_0 结构体实例的初始化部分代码。

    static struct __main_block_desc_0 __main_block_desc_0_DATA = {
    	0sizeof(struct __main_block_impl_0)
    };
    
    • 1
    • 2
    • 3
    • 4

    我们来确认一下使用该 Block的部分。

    blk();
    
    • 1

    这部分可变换为以下源代码:

    ((void (*)(struct __block_impl *))(
    (struct __block_impl *)blk)->FuncPtr)((struct_block_impl *)blk);
    
    • 1
    • 2

    去掉转换部分。

    (*blk->impl.FuncPtr)(blk);
    
    • 1

    这就是简单地使用函数指针调用函数。正如我们刚才所确认的,由Block 语法转换的_ main_block_func_0函数的指针被赋值成员变量FuncPtr中。另外也说明了, _main_block_func_0函数的参数__cself指向Block值。在调用该函数的源代码中可以看出Block正是作为参数进行了传递。

    其实,所谓Block 就是Objective-C 对象。

    截获自动变量

    int main(int argc, const char * argv[]) {
    	int dmy = 256;
        int val = 10;
        const char  *fmt = "val = %d\n";
        void (^blk)(void) = ^{
        	printf(fmt, val);
        };
        blk();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    struct __block_impl {
      	void *isa;
      	int Flags;
      	int Reserved;
      	void *FuncPtr;
    };
    
    struct __main_block_impl_0 {
      	struct __block_impl impl;
      	struct __main_block_desc_0* Desc;
      	const char *fmt;
      	int val;
      	__main_block_impl_0(void *fp, struct __main_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 __main_block_func_0(struct __main_block_impl_0 *__cself {
      	const char *fmt = __cself->fmt;
      	int val = __cself->val; 
      
    	printf(fmt, val);
    }
    
    static struct __main_block_desc_0 {
      	size_t reserved;
      	size_t Block_size;
    } __main_block_desc_0_DATA = {
    	0, 
    	sizeof(struct __main_block_impl_0)
    };
    
    int main(int argc, const char * argv[]) {
    	int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
        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

    其中Block语法表达式中使用的自动变量被作为成员变量追加到了__main_block_impl_0结构体中。

    struct __main_block_impl_0 {
      	struct __block_impl impl;
      	struct __main_block_desc_0* Desc;
      	const char *fmt;
      	int val;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    _ _main_block_impl_0结构体内声明的成员变量类型与自动变量类型完全相同。
    请注意 Block 语法表达式中没有使用的自动变量不会被追加
    初始化该结构体实例的构造函数的差异:

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    
    • 1
    //通过构造函数调用确认其参数
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    
    • 1
    • 2

    使用执行Block语法时的自动变量fmt 和 val来初始化__main_block_impl_0结构体实例。即在该源代码中,__main_block_impl_0结构体实例的初始化如下:

    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    fmt = "val = %d\n";
    val = 10;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    初始化时对fmt和val进行了赋值。由此可知,在__main_block_impl_0结构体实例中(即Block),自动变量被截获。
    再看一下使用Block的匿名函数的实现:

    ^{printf(fmt, val)};
    
    • 1

    转换为:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      	const char *fmt = __cself->fmt;
      	int val = __cself->val;
      	
    	printf(fmt, val);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在转换后的源代码中,截获到__main_block_impl_0 结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动便可使用截获的自动变量值执行。

    在Block 中利用C语言数组类型的变量时有可能使用到的源代码。首先来看将数组传递给Block的结构体构造函数的情况。

    void func(char a[10]) {
    	printf("%d\n",a[0]);
    }
    int main() {
    	char a[10] = {2};
    	func(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    该源代码可以顺利编译,并正常执行。在之后的构造函数中,将参数赋给成员变量中,这样在变换了Block语法的函数内可由成员变量赋值给自动变量。源代码预测如下。

    void func(char a[10]) {
    	char b[10] = a;
    	printf("%d\n", b[0]);
    }
    int main() {
    	char a[10] = {2};
    	func(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    该源代码将C语言数组类型变量赋值给C语言数组类型变量中,这是不能编译的。虽然变量的类型以及数组的大小都相同,但C语言规范不允许这种赋值。当然,有许多方法可以截获值,但Blocks 似乎更遵循C语言规范。

    __Block说明符

    Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
    以下源代码试图改变Block中的自动变量val。该代码会产生编译错误。

    int val = 0;
    void(^blk)(void) = ^{val = 1;};
    
    • 1
    • 2

    因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量赋值的操作时,便产生编译错误。
    不过这样一来就无法在Block中保存值了,极为不便。解决这个问题有两种方法。第一种:C语言中有一个变量,允许Block改写值。具体如下:

    • 静态变量
    • 静态全局变量
    • 全局变量
      静态变量的这种方法似乎也适用于自动变量的访问。但是我们为什么没有这么做呢?
      实际上,在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃,因此 Block 中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量。这些在下节详细说明。
      解决Block中不能保存值这一问题的第二种方法是使用“_ block说明符”。更准确的表述方式为“ block存储域类说明符”( _block storage-class-specifier)。C语言中有以下存储域说明符:
    • typedef
    • extern
    • static
    • auto
    • register
      _block 说明符类似于 static、auto 和 register 说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto 表示作为自动变量存储在栈中,static 表示作为静态变量存储在数据区中。
      下面我们来实际使用
      block说明符,用它来指定Block中想变更值的自动变量。我们在前面编译错误的源代码的自动变量声明上追加 _block 说明符。
    __block int val = 10;
    void (^blk)(void) = ^{val = 1;};
    
    • 1
    • 2

    变换后:

    struct __Block_byref_val_0 {
    	void *__isa;
    	__Block_byref_val_0 *__forwarding;
    	int __flags;
    	int __size;
    	int val;
    };
    struct __main_block impl_0 {
    	struct __block_impl impl;
    	struct __main block desc 0* Desc;
    	__Block_byref_val_0 *val;
    	__main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags=0) : val(_val->__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_val_0 *val =__cself->val;
    	
    	(val->__forwarding->val) = 1;
    }
    static void_main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    	_Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
    }
    
    static void __main_block_dispose_0(struct __main_block_imp1_0*src) {
    	_Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
    }
    
    static struct __main_block_desc_0 {
    	unsigned long reserved;
    	unsigned long Block_size;
    	void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    	void (*dispose)(struct __main_block_impl_0*);
    }__main_block_desc_0_DATA = {
    	0,
    	sizeof(structmain_block_impl_0),
    	__main_block_copy_O,
    	__main_block_dispose_0
    };
    int main() {
    	__Block_byref_val_0 val = {
    		0,
    		&val,
    		0,
    		sizeof(__Block_byref_val_0),
    		10
    	};
    	blk = &__mainblock_impl_0(
    __main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    
    	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

    这个__block变量val是怎样转换过来的呢?

    __Block_byref_val_0 val = {
    	0,
    	&val,
    	0,
    	sizeof(_Block_byref_val_0),
    	10
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    它竟然变为了结构体实例。__block变量也同 Block一样变成__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0 结构体实例。该变量初始化为10,且这个值也出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。

  • 相关阅读:
    flutter ios Firebase 消息通知错误 I-COR000005,I-FCM001000 解决
    【C++】顺序表,链表,栈的练习(千万要会做)每日小细节007
    el-table <template slot=“header“>不更新问题
    C++11的lambda表达式
    C++重新入门-string容器
    仿闪照功能娱乐微信小程序源码下载-带外卖CPS功能和流量主
    发布 markdown 小功能:指定图片尺寸
    蓝桥杯动态规划集齐图案
    AI全球气象预报模型;开源数据标注平台;『统计学习导论及R语言应用』Python版源码;『数学』自学路线图与资源;前沿论文 | ShowMeAI资讯日报
    animate动画库的使用步骤
  • 原文地址:https://blog.csdn.net/m0_73348697/article/details/136792968