美文网首页
Block学习

Block学习

作者: Tony17 | 来源:发表于2020-02-25 17:42 被阅读0次

    前言

    Block 是 C 语言的扩充功能,可以用一句话来表示Blocks的扩充功能:带有局部变量(自动变量)的匿名函数。
    Block 是在开发过程中使用非常频繁。它可以在需要使用的时候直接调用,非常好的保持了上下文的关系。但是我对于它的具体实现一直不是很清楚。
    简单调用代码:

    #define block ^{NSLog(@"===== block1 age: %d", age);}
    
    #import <Foundation/Foundation.h>
    static int weight = 20;
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void(^block1)(void)= ^{
                NSLog(@"===== block1");
            };
            int age = 10;
            void(^block2)(void)= block;
            void(^block3)(void)= ^{
                NSLog(@"===== block2, weight: %d", weight);
            };
            NSLog(@"\n直接创建: \t%@\n直接创建+copy: \t%@\n创建并无调用变量: \t%@\n创建并调用局部变量: \t%@\n创建并调用全局变量: \t%@",[block class], [[block copy] class], [block1 class], [block2 class], [block3 class]);
        }
        return 0;
    }
    

    输出内容:

    直接创建:   __NSStackBlock__
    直接创建+copy:  __NSMallocBlock__
    创建并无调用变量:   __NSGlobalBlock__
    创建并调用局部变量:  __NSMallocBlock__
    创建并调用全局变量:  __NSGlobalBlock__
    

    字段说明

    匿名函数

    Block 表达式

    ^ 返回值类型 参数列表 表达式

    • ^ 是插入记号,表示将要插入一个 Block
    • 返回值类型 可以省略,但是如果表达式中有返回值,那么返回值类型一定要和返回值类型一致
    • 参数列表 可以省略,说明调用时没有参数传递

    所以Block 的常见写法有如下几种:

    ^int (int count) {return count + 1;}    // 有返回值类型,有参数列表
    ^(int count) {return count + 1;}        // 没有有返回值类型,有参数列表
    ^int {return 1;}          // 有返回值类型,没有参数列表
    ^ {NSLog(@"Block");}    // 没有有返回值类型,没有参数列表
    

    Block 类型

    类型 名称 存放位置 环境
    NSGlobalBlock 全局Block data区 - 全局访问 没有访问auto变量
    NSStackBlock 栈Block 栈区 - 自动释放 访问了auto变量
    NSMallocBlock 堆Block 堆区 - 手动释放 栈Block 调用了 copy

    一般来说,在 ARC 模式下,把 Block 赋值给变量的时候,系统会自动调用 copy 操作把 Block 复制到堆上,所以基本上不会访问到栈Block的。这里把 Block 放在堆上的主要原因就是 栈区的变量在超出生命周期之后自动释放,很容易导致 Block 还没有调用就已经释放了。

    自动变量

    在 Block 中,表达式会截获所有使用的自动变量的值(保存该自动变量的瞬时值)。这个值的捕获时机是在 Block 定义的时候,所以在 Block 定义后再修改变量的值是不会对 Block 内部使用的值造成影响的。

    但是 Block 自动捕获的变量是不可以修改的。如果需要修改这个变量并且保持 Block 内外值一致。就需要在外部定义变量的时候使用 __block 来修饰。这里需要注意一下,操作可变对象是可以的。给可变对象重新赋值是不可以的。

    NSMutableArray *array = [NSMutableArray new];
    void (^blk)(void) = ^ {
        [array addObject:[NSObject new]];
    };
    blk();
    NSLog(@"%lu", (unsigned long)array.count);
    

    这段代码是没有问题的,因为虽然是在修改变量 array 的内部值,但是 array 本身并没有变。

    NSMutableArray *array = [NSMutableArray new];
    void (^blk)(void) = ^ {
        array = [NSMutableArray new];
    };
    blk();
    NSLog(@"%lu", (unsigned long)array.count);
    

    这段代码是会报错,xcode 的错误信息是 Variable is not assignable (missing __block type specifier)。因为这里是给变量 array 重新赋值了。

    另外,在使用 Block 的时候要注意 C 语言的数组,由于自动捕获机制没有针对 C 语言数据的实现。所以如果有这方面的需求的话,需要使用指针来解决这个问题。

    Block 实质

    通过查看源码可以知道,Block 的大致结构如下:

    struct __Block_byref_age_0 {
      void *__isa;
    __Block_byref_age_0 *__forwarding;
     int __flags;
     int __size;
     int age;
    };
    struct __Block_byref_obj_1 {
      void *__isa;
    __Block_byref_obj_1 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *obj;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_obj_1 *obj; // by ref
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_1 *_obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj->__forwarding), age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    __block

    在开发过程中,如果想让 block 内外的变量值保持一致,就需要使用 __block 来修饰定义的变量。

    __block 修饰的变量最大的特点是在生成的结构体中存在 __forwarding 变量,这个变量始终指向变量的实际地址。例如 Block 从栈copy到堆上的时候,栈区定义的变量值也会复制到堆上,所以对应的__block 结构体中的 __forwarding 指向堆中的地址。这样每次访问这个变量的时候,都会访问 __forwarding 指针指向的内存地址的值,从而保持值一致。

    __weak

    block使用过程中,最常见的一个问题就是循环引用,循环引用的定义和引发的后果这里就不多做阐述。而避免循环引用的方式也很简单,就是在外部使用 __weak 修饰符申明一下,block 内部引用的是这个 __weak 修饰的变量。但是这样也会有一个问题,就是如果在 block内部调用的时候,这个对象有可能已经释放了。这样会导致原本的逻辑遭到破坏,这时候有人提出来,完美的解决办法就是 block 外部使用 __weak 修饰一下,block 内部 使用 __strong 重新赋值一下,这样就可以在使用的时候保证这个值的存在了。

    最后

    欢迎斧正~

    相关文章

      网友评论

          本文标题:Block学习

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