Block解读

作者: 偶尔登南山 | 来源:发表于2019-04-18 10:22 被阅读2次

    Block定义

            看过<<Objective-C高级编程iOS与OS X多线程和内存管理>>这本书的朋友应该知道其对block的定义:Blocks是C语言的扩充功能,带有自动变量(局部变量)的匿名函数.匿名函数就是不带有名称的函数.Block在不同的语言中有不同的命名.Python中叫Lambda,Swift中叫Closure(闭包)等.

    我们知道根据作用域的不同变量大概有:自动变量(局部变量),静态变量(静态局部变量),静态全局变量,全局变量.而静态变量(局部和全局),全局变量其生命周期一般与程序同步,在程序的整个生命周期内,其也会保持在同一内存区域.

    block的声明结构

    
    // MARK: - blcok声明结构
    
    struct Block_layout {
    
    void *isa;
    
    volatile int32_t flags; // contains ref count
    
    int32_t reserved;
    
    void (*invoke)(void *, ...);
    
    struct Block_descriptor_1 *descriptor;
    
    // imported variables
    
    };
    
    // MARK: - block 附件信息1
    
    #define BLOCK_DESCRIPTOR_1 1
    
    struct Block_descriptor_1 {
    
    uintptr_t reserved;
    
    uintptr_t size;
    
    };
    
    // MARK: - block 附件信息2
    
    #define BLOCK_DESCRIPTOR_2 1
    
    struct Block_descriptor_2 {
    
    // requires BLOCK_HAS_COPY_DISPOSE
    
    void (*copy)(void *dst, const void *src);
    
    void (*dispose)(const void *);
    
    };
    
    // MARK: - block 附件信息3
    
    #define BLOCK_DESCRIPTOR_3 1
    
    struct Block_descriptor_3 {
    
    // requires BLOCK_HAS_SIGNATURE
    
    const char *signature;
    
    const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    
    };
    
    struct Block_byref {
    
    void *isa;
    
    struct Block_byref *forwarding;
    
    volatile int32_t flags; // contains ref count
    
    uint32_t size;
    
    };
    
    struct Block_byref_2 {
    
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    
    void (*byref_destroy)(struct Block_byref *);
    
    };
    
    struct Block_byref_3 {
    
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    
    const char *layout;
    
    };
    
    

    只在block_private.h中只找到block相关的声明结构,具体实现没有找到.其实现源码这里.下面我们看一下block_layout声明结构中的一些成员变量:

    isa:block类型指针

    flags:标志位,用于按bit操作

    reserved:保留变量

    invoke:指向block实现函数的指针

    descriptor:附件信息

    这些具体的结构我们可以通过clang编译器重写Objective-C文件转成.cpp文件查看源码.

    Block语法

    返回值类型 (^block名称)(形参参数列表) = ^返回值类型(参数列表){

    return 返回值

    }

    Block语法.jpg

    Block截获自动变量

    带有自动变量的值在block中表现为截获自动变量值.

    
    int valueOne = 18;
    
    int valueTwo = 1;
    
    // MARK: - 截获自动变量
    
    void(^BlockOne)(void) = ^void(){
    
    NSLog(@"valueOne:%d; valueTwo:%d",valueOne,valueTwo);
    
    };
    
    // 修改自动变量的值
    
    valueOne = 9;
    
    // 调用block
    
    BlockOne();
    
    

    调用block后我们看到log输出:

    
    2019-04-17 15:30:28.442314+0800 Block_Layout[6530:443419] valueOne:18; valueTwo:1
    
    

            block内部捕获了valueOne和valueTwo两个自动变量,其中valueOne在block调用前进行了修改,而在block在真实调用时,valueOne值并没有跟随修改,因为block表达式截获得是自动变量的瞬间值,保存后就不能改写.所以在调用block时,即使改了block截获的自动变量的值,也不会影响block表达式内截获的值.

    通过clang编译器重写一些文件,获取底层实现源码:

    
    // block 声明结构
    
    struct __block_impl {
    
    void *isa;
    
    int Flags;
    
    int Reserved;
    
    void *FuncPtr;
    
    };
    
    // block声明&实现结构
    
    void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, valueOne, valueTwo));
    
    valueOne = 9;
    
    // block调用
    
    ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);
    
    // MARK: - block具体实现结构
    
    struct __ViewController__viewDidLoad_block_impl_0 {
    
    struct __block_impl impl;
    
    struct __ViewController__viewDidLoad_block_desc_0* Desc;
    
    int valueOne;
    
    int valueTwo;
    
    // 同名的构造函数
    
    __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _valueOne, int _valueTwo, int flags=0) : valueOne(_valueOne), valueTwo(_valueTwo) {
    
    impl.isa = &_NSConcreteStackBlock;
    
    impl.Flags = flags;
    
    impl.FuncPtr = fp;
    
    Desc = desc;
    
    }
    
    };
    
    // MARK: - block实现结构对应的实现函数
    
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    
    int valueOne = __cself->valueOne; // bound by copy
    
    int valueTwo = __cself->valueTwo; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_75dfcb_mi_0,valueOne,valueTwo);
    
    }
    
    // MARK: - desc结构
    
    static struct __ViewController__viewDidLoad_block_desc_0 {
    
    size_t reserved;
    
    size_t Block_size;
    
    } __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
    
    

    具体看一下这几个结构:

    __block_impl:block对外暴露的声明结构,内部有四个成员变量.

    _block_impl_0:block内部实现结构,其内部有一个同名的构造函数.

    _block_func_0:block实现结构对应的实现函数,我们对block捕获的一些值都在这里操作,其参数为__cself指向的是Block自身.

    _block_desc_0:desc结构,描述_block_impl_0的size等.

    __ViewController__viewDidLoad_block_impl_0()构造函数,将fp函数指针赋值给impl的FuncPtr,将desc赋值给 Desc ,将flags赋值给impl的flags,将Block类型赋值给isa,之前我们已经讲过isa,可以具体查看,到此我们可以确定block的本质其实就是Objective-C对象,这些赋值后完成对impl进行初始化.

    blockOne就是一个_block_impl_0的实例.

            可以以方法类比:__block_impl是对外的接口,其内部实现是_block_impl_0来实现的.我们可以看到block实现表达式中捕获的直接是valueOne,valueTwo的值.

    如果我们尝试在block内部修改其捕获的自动变量的值,或马上报错:

    missing__block_error.png

    告诉我们缺少__block修饰符,下面我们在看一个情况:

    
    NSMutableArray *array = [[NSMutableArray array]init];
    
    // MARK: - 截获自动变量
    
    void(^BlockOne)(void) = ^void(){
    
    [array addObject:[NSObject new]];
    
    };
    
    

    这种情况可以正常编译过去,再看一下:

    missing__block.png

    同样的错误.

            然后我们用__block修饰符修饰一下自动变量,编译正常通过.因此,如果我们需要修改block捕获的自动变量,需要在自动变量前面添加__block修饰符.

    Block捕获静态/全局变量

    
    // 静态全局变量
    
    static int valueTwo = 20;
    
    // 全局变量
    
    int valueThree = 30;
    
    @interface ViewController ()
    
    @property (nonatomic, copy) NSString *name;
    
    @property (nonatomic, copy) NSString *valueString;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
    
    [super viewDidLoad];
    
    // 静态局部变量
    
    static int valueOne = 10;
    
    // MARK: - 截获静态变量,全局变量
    
    void(^BlockOne)(void) = ^void(){
    
    valueOne = 1;
    
    valueTwo = 2;
    
    valueThree = 3;
    
    NSLog(@"valueOne:%d,valueTwo:%d,valueThree:%d",valueOne,valueTwo,valueThree);
    
    };
    
    // 修改自动变量的值
    
    valueOne = 9;
    
    // 调用block
    
    BlockOne();
    
    }
    
    @end
    
    

    日志输出:

    
    2019-04-17 16:56:57.112848+0800 MTTRuntime[7888:540053] valueOne:1,valueTwo:2,valueThree:3
    
    

    然后我们继续用clang编译转换一下:

    
    // block 声明和实现结构
    
    void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, &valueOne));
    
    valueOne = 9;
    
    // block调用
    
    ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);
    
    struct __ViewController__viewDidLoad_block_impl_0 {
    
    struct __block_impl impl;
    
    struct __ViewController__viewDidLoad_block_desc_0* Desc;
    
    int *valueOne;
    
    __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int *_valueOne, int flags=0) : valueOne(_valueOne) {
    
    impl.isa = &_NSConcreteStackBlock;
    
    impl.Flags = flags;
    
    impl.FuncPtr = fp;
    
    Desc = desc;
    
    }
    
    };
    
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    
    int *valueOne = __cself->valueOne; // bound by copy
    
    // 静态局部变量
    
    (*valueOne) = 1;
    
    // 静态全局变量
    
    valueTwo = 2;
    
    // 全局变量
    
    valueThree = 3;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_9483ca_mi_0,(*valueOne),valueTwo,valueThree);
    
    }
    
    static struct __ViewController__viewDidLoad_block_desc_0 {
    
    size_t reserved;
    
    size_t Block_size;
    
    } __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
    
    

            我们可以看到静态全局变量和全局变量,被block捕获后直接修改的是其值,因为全局变量作用域于全局,在block内部进行修改后其值可以被保存下来.

            而静态局部变量访问的是使用指针对其进行访问,在block实现表达式里面传进去的也是&valueOne地址.

    block在捕获全局变量/静态变量时,可以在block实现表达式内部对其进行更改.block捕获静态局部变量的地址并保存,通过操作指针实现值的修改.

    Block捕获__block修饰的自动变量

    
    - (void)viewDidLoad {
    
    [super viewDidLoad];
    
    // __block修饰的自动变量
    
    __block int valueOne = 10;
    
    // MARK: - 截获__block修饰的自动变量
    
    void(^BlockOne)(void) = ^void(){
    
    valueOne = 1;
    
    NSLog(@"valueOne:%d",valueOne);
    
    };
    
    // 修改自动变量的值
    
    valueOne = 9;
    
    // 调用block
    
    BlockOne();
    
    }
    
    

    控制台输出:

    
    2019-04-18 08:54:48.274042+0800 MTTRuntime[4012:43868] valueOne:1,valueTwo:20,valueThree:30
    
    

    被__block修饰的自动变量,block捕获后能够对其进行更改.clang重写一下,查看源码:

    
    // __block修饰的成员变量转换
    
    __attribute__((__blocks__(byref))) __Block_byref_valueOne_0 valueOne = {(void*)0,(__Block_byref_valueOne_0 *)&valueOne, 0, sizeof(__Block_byref_valueOne_0), 10};
    
    // block的声明和实现表达式
    
    void(*BlockOne)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_valueOne_0 *)&valueOne, 570425344));
    
    // 修改valueOne的值
    
    (valueOne.__forwarding->valueOne) = 9;
    
    //block调用
    
    ((void (*)(__block_impl *))((__block_impl *)BlockOne)->FuncPtr)((__block_impl *)BlockOne);
    
    // byref结构
    
    struct __Block_byref_valueOne_0 {
    
    void *__isa;
    
    __Block_byref_valueOne_0 *__forwarding;//指向自己的指针
    
    int __flags;
    
    int __size;
    
    int valueOne;
    
    };
    
    //block匿名实现结构
    
    struct __ViewController__viewDidLoad_block_impl_0 {
    
    struct __block_impl impl;
    
    struct __ViewController__viewDidLoad_block_desc_0* Desc;
    
    __Block_byref_valueOne_0 *valueOne; // by ref
    
    __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_valueOne_0 *_valueOne, int flags=0) : valueOne(_valueOne->__forwarding) {
    
    impl.isa = &_NSConcreteStackBlock;
    
    impl.Flags = flags;
    
    impl.FuncPtr = fp;
    
    Desc = desc;
    
    }
    
    };
    
    // block实现函数
    
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    
    __Block_byref_valueOne_0 *valueOne = __cself->valueOne; // bound by ref
    
    (valueOne->__forwarding->valueOne) = 1;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_ea5a30_mi_0,(valueOne->__forwarding->valueOne));
    
    }
    
    // block copy
    
    static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->valueOne, (void*)src->valueOne, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    // block dispose
    
    static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->valueOne, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    

            可以看到,这里多了__Block_byref_valueOne_0结构,我们初始化的自动变量也被转换成了这个结构的实例,自动变量初始化值为10,这个值也出现在__Block_byref_valueOne_0结构的初始化中,并且这个结构持有这个变量,使其成为成员变量.

    
    // byref结构
    
    struct __Block_byref_valueOne_0 {
    
    void *__isa;
    
    __Block_byref_valueOne_0 *__forwarding;//指向自己的指针
    
    int __flags;
    
    int __size;
    
    int valueOne;
    
    };
    
    

    我们再来看一下怎么访问这个自动变量的:

    
    // block实现函数
    
    static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
    
    __Block_byref_valueOne_0 *valueOne = __cself->valueOne; // bound by ref
    
    (valueOne->__forwarding->valueOne) = 1;
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_0qh5fq653wq1l3qz9_3ftg3r0000gn_T_ViewController_ea5a30_mi_0,(valueOne->__forwarding->valueOne));
    
    }
    
    

            是通过((__Block_byref_valueOne_0 *)valueOne->__forwarding->valueOne).Block的_block_impl_0结构实例指针持有指向__Block_byref_valueOne_0的指针;而__Block_byref_valueOne_0的成员变量持有指向自身的指针.然后通过__forwarding来访问valueOne.__Block_byref_valueOne_0结构并不在_block_impl_0中,这样做主要是在多个Block中使用__block.

    Block类型

    _NSConcreteGlobalBlock:定义在全局区,存储于程序数据区

    _NSConcreteStackBlock:存储于栈区,超出作用域被回收

    _NSConcreteMallocBlock:存储于堆区,从栈copy过来

    Block复制到堆上的情况:

    1)block调用了copy方法;

    2)block作为方法返回值;

    3)将block赋值给__strong修饰的id类型或赋值给类的block成员变量;

    4)方法名中有usingBlock的cocoa框架或者GCD相关api;

    Block循环引用

            如果Block中使用附有__strong修饰符的对象类型自动变量,那么Block从栈复制到堆上时,该对象为Block所持有,这样会引起循环引用问题,可以通过__weak修饰符来打破循环引用,这个我们在后面细讲__weak实现原理.

    编译后源码库

    编译后的源码放在Github, 如果对你有帮助,请给一个star吧!

    博客地址&相关文章

    博客地址: https://waitwalker.cn/

    系列文章:

    1. Runtime源码编译

    2. objc_object解读

    3. Method解读

    4. Class解读

    5. Ivar objc_property_t Protocol解读

    6. Block解读

    7. Retain&Release解读

    8. Autorelease解读

    9. Weak解读

    相关文章

      网友评论

        本文标题:Block解读

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