美文网首页
block底层实现

block底层实现

作者: GemShi | 来源:发表于2018-05-18 19:05 被阅读208次

    最近读书,关于block的底层实现,有以下思考和总结

    • c++部分的相关步骤分析写在代码注释
    一、block是什么

    1.首先写一个简单的block

    #import <stdio.h>
    
    int main(void) {
        
        void (^block)(void) = ^{
            printf("hello world!");
        };
        
        block();
        
        return 0;
    }
    

    2.将main.m 编译后 clang -rewite-objc main.m 生成 .cpp 文件

    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;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    /**
    3.参数fp即传入的参数__main_block_func_0函数的地址,赋值给结构体实例 impl 的属性 FuncPtr
    4.参数desc即传入的 &__main_block_desc_0_DATA结构体取地址赋值给了结构体指针 Desc
    5.结构体实例 impl 的 isa 指针存放了 _NSConcreteStackBlock类的地址
    6._NSConcreteStackBlock是在将block作为OC对象处理时,该类的信息放置于_NSConcreteStackBlock 中,
    由此可见,block的实质是OC对象
    */
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
    
            printf("hello world!");
        }
    
    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(void) {
    
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    /**
    对应代码
    void (^block)(void) = ^{
        printf("hello world!");
    };
    1.等号左边 void (*block)(void)是一个无参无返的函数指针(是一个指针,指向函数)
    2.等号右边 __main_block_impl_0 首先,c++结构体包含自己的属性,构造方法,成员方法,
    所以 &__main_block_impl_0()是对结构体构造函数取地址,
    函数的参数是 (void *)__main_block_func_0 和 &__main_block_desc_0_DATA
    */
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    /**
    对应代码 block();
    7.前半部分((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
    访问结构体 __block_impl自己的成员变量FuncPtr,
    FuncPtr在步骤3被赋值了 __main_block_func_0(block执行代码块)函数的地址
    8.后半部分((__block_impl *)block),将block自身作为参数传递
    */
    
        return 0;
    }
    

    3.引入变量

    int main(void) {
        
        int a = 10;
        void (^block)(void) = ^{
            printf("%d\n",a);
        };
        block();
        
        return 0;
    }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    /**
    2.在c++中 a(_a) 表示 _a 这个形参赋值给 a 这个实参
    */
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
    /**
    3.定义了一个新的变量,接收结构体成员变量a的值,__cself->a 表示结构体访问自己的属性。
    */
            printf("%d\n",a);
        }
    
    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(void) {
    
        int a = 10;
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    /**
    1.将变量a的值传入,并赋值给了结构体成员变量
    */
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    

    4.__block变量

    int main(void) {
        
        __block int a = 10;
        void (^block)(void) = ^{
            a += 10;
            printf("%d\n",a);
        };
        
        block();
        
        return 0;
    }
    
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;  //由步骤2得出指向的结构体本身
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    /**
    4.将传入的结构体指针的成员变量__forwarding的值(地址),赋值给当前结构体成员变量结构体指针a,保存了__block变量地址
    */
        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_a_0 *a = __cself->a; // bound by ref
    /**
    5.访问结构体成员变量a,赋值给新的结构体指针
    */
            (a->__forwarding->a) += 10;
    /**
    6.a->__forwarding首先找到结构体指针的指向,a->__forwarding->a获取到有效成员变量a的值并进行修改
    */
            printf("%d\n",(a->__forwarding->a));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    /**
    7.栈copy到堆时调用,因为结构体成员变量包含所截获的变量或者__block变量结构体指针,所以copy的时候也一起copy了
    */
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    /**
    8.堆上的block废弃时调用
    */
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t 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(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(void) {
    
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    /**
    1.变量a不再单纯的是一个int类型,变成了__Block_byref_a_0结构体类型,并分别传入的参数进行赋值
    2.第二个参数(__Block_byref_a_0 *)&a,结构体指针,值是a的地址,a又是结构体__Block_byref_a_0,
    所以__Block_byref_a_0中的结构体指针__forwarding指向自身
    3.C++中,__attribute__ 表示可以设置函数属性。
    理解一下字面意思大概知道内部发生了什么
    byref 是把内存地址告诉程序,所以改变的直接就是内存中的数值。按地址传递参数使过程用变量的内存地址去访问实际变量的内容。结果,将变量传递给过程时,通过过程可永远改变变量值。
    byval 是把内存数值的拷贝给程序,所以改变的只是拷贝,内存原来的值是不会改变的。按值传递参数时,传递的只是变量的副本。如果过程改变了这个值,则所作变动只影响副本而不会影响变量本身。
    */
    
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    

    📍结论:
    1.block是函数指针,指向的是block代码块编译时生成的函数地址。执行block相当于找到指向block的指针。
    2.block对象是结构体
    (1)有isa指针(impl->isa)指向自己的类(_NSConcreteStackBlock)
    (2)有desc结构体描述block的信息
    (3)__forwarding((__Block_byref_a_0 *)a -> __forwarding)指向自己或堆上自己的地址
    (4)当block对象截获变量,变量也会出现在block结构体中(int a)
    (5)指向block代码块的函数指针(impl.FuncPtr = fp)。
    (6)block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)
    3.block代码块在编译的时候会生成一个函数,函数的参数是block对象结构体指针。执行block,相当于通过block的地址找到__block变量结构体指针,再找到变量值,进行操作。

    二、block的类

    1.block在编译中,会被当成结构体处理,block实际结构

    structBlock_literal_1{
        void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
        intflags;
        intreserved;
        void(*invoke)(void *,...);    //block执行时调用的函数指针,block定义时内部的执行代码
        structBlock_descriptor_1{
            unsigned long intreserved;         // NULL
            unsigned long intsize;         // sizeof(struct Block_literal_1)
            // optional helper functions
             void(*copy_helper)(void *dst, void *src);     // IFF (1<<25)
             void(*dispose_helper)(void *src);             // IFF (1<<25)
             // required ABI.2010.3.16
             const char *signature;                         // IFF (1<<30)
         } *descriptor;
         // imported variables
    };
    

    2.block的类有三种

    block的类 设置对象的存储域
    _NSConcreteGlobalBlock 程序的数据区域(.data区)
    _NSConcreteStackBlock
    _NSConcreteMallocBlock
    应用程序的内存分配

    (1)_NSConcreteGlobalBlock

    #import <Foundation/Foundation.h>
    
    void (^block)(void) = ^{
        
    };
    
    int main(void) {
        
        void (^block1)(void) = ^{
            
        };
        
        NSLog(@"%s %p",object_getClassName(block),block);
        NSLog(@"%s %p",object_getClassName(block1),block1);
        
        return 0;
    }
    
    //输出
    //__NSGlobalBlock__ 0x100001058
    //__NSGlobalBlock__ 0x100001098
    

    (2)_NSConcreteStackBlock
    当block引入变量,变量内存地址会发生如下变化

    #import <Foundation/Foundation.h>
    
    int main(void) {
        
        int a = 10;
        void (^block)(void) = ^{
            printf("%d\n",a);
        };
        
        NSLog(@"%s %p",object_getClassName(block),block);
        NSLog(@"%@",^{
            printf("%d\n",a);
        });
        
        return 0;
    }
    //输出
    //__NSMallocBlock__ 0x100443c80
    //<__NSStackBlock__: 0x7fff5fbff6b0>
    

    (3)_NSConcreteMallocBlock
    从上一步可以看出,block在被赋值后,从栈来到了堆,这段代码是从栈copy到堆的过程

    static void *_Block_copy_internal(const void *arg, const int flags) {
        struct Block_layout *aBlock;
        ...
        aBlock = (struct Block_layout *)arg;
        ...
        // Its a stack block.  Make a copy.
        if (!isGC) {
            // 申请block的堆内存
            struct Block_layout *result = malloc(aBlock->descriptor->size);
            if (!result) return (void *)0;
            // 拷贝栈中block到刚申请的堆内存中
            memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 1;
            // 改变isa指向_NSConcreteMallocBlock,即堆block类型
            result->isa = _NSConcreteMallocBlock;
            if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
                //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
                (*aBlock->descriptor->copy)(result, aBlock); // do fixup
            }
            return result;
        }
        else {
            ...
        }
    }
    

    block是默认分配在栈上的唯一OC对象,因为编译器更倾向于在栈上分配空间,因为执行效率较高,但是只能是在已知实际大小的情况下去分配,只有简单的值(比如指针)可以被分配在栈上,block的大小是确定的,你创建了一个给定的block就不能修改, 在整个执行过程中block不变, 它是需要快速执行的代码段,因此它是栈的最佳候选。

    📍结论:
    1.定义在函数外的block和定义在函数内部且没有捕获自动变量的block是全局block。
    2.ARC下并且有变量捕获的情况下,对block自动执行了copy,将block由栈---->copy---->堆
    3.copy的过程中,主要实现,通过memmove函数将block内容进行copy,并且将 isa 指针指向了_NSConcreteMallocBlock

    三、block的copy

    1.自身的copy
    (1)栈Block
    栈block拷贝复制了内容,重置了isa指针指向,重置了flags参数

    struct Block_layout *result = malloc(aBlock->descriptor->size);
          if (!result) return (void *)0;
          memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
          // reset refcount
          result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
          result->flags |= BLOCK_NEEDS_FREE | 1;
          result->isa = _NSConcreteMallocBlock;
          if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
              //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
              (*aBlock->descriptor->copy)(result, aBlock); // do fixup
          }
          return result;
    

    (2)堆Block
    改变引用计数

    if (aBlock->flags & BLOCK_NEEDS_FREE) {
          // latches on high
          latching_incr_int(&aBlock->flags);
          return aBlock;
      }
    

    (3)全局Block
    直接返回了传入的block

    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
          return aBlock;
      }
    

    2.__block变量的copy
    (1)__block修饰的基本数据类型变量
    会生成__Block_byref_a_0结构体,__block将基本数据类型包装成对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。
    (2)没有用__block修饰的对象变量

        NSObject *obj = [[NSObject alloc] init];
        void(^block)(void) = ^{
            NSLog(@"%@",obj);
        };
        block();
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      NSObject *obj;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      NSObject *obj = __cself->obj; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_8ef076_mi_0,obj);
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t 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(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(void) {
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    

    并没有生成__Block_byref_a_0结构体,在_Block_object_assign中对应的判断代码:

    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
    }
    

    通过以上代码可以看出,对对象进行了retain,操作了对象的引用计数
    (3)用__block修饰的对象变量

        __block NSObject *obj = [[NSObject alloc] init];
        void(^block)(void) = ^{
            NSLog(@"%@",obj);
        };
        block();
    
    struct __Block_byref_obj_0 {
      void *__isa;
    __Block_byref_obj_0 *__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_0 *obj; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__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_obj_0 *obj = __cself->obj; // bound by ref
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_ae30ca_mi_0,(obj->__forwarding->obj));
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t 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(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(void) {
        __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
        return 0;
    }
    

    生成了__Block_byref_obj_0结构体,并且结构体中多了两个成员函数,void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*); 用来实现对对象的内存管理。copy/dispose可以理解为retain(copy)/release

    四、block的循环引用
    #import "Person.h"
    
    typedef void(^block)(void);
    
    @implementation Person
    {
        block _blk;
    }
    
    -(void)test
    {
        _blk = ^{
            NSLog(@"%@",self);  //⚠️warning - retainCycle
        };
    }
    
    @end
    

    这段代码会报warning⚠️,因为造成循环引用retainCycle


    相互持有

    解决
    1.__weak一端变为弱引用(MRC下无效)

        __weak typeof(self)wself = self;
        _blk = ^{
            NSLog(@"%@",wself);
        };
    
    __weak弱引用

    2.引入__block变量(ARC下无效)
    当block由栈copy到堆,若block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain
    这段代码在ARC下不会走dealloc方法

        __block id obj = self;
        _blk = ^{
            NSLog(@"%@",obj);
        };
    
    引入__block变量

    以上是我个人分析,有不合理的地方,欢迎指正

    相关文章

      网友评论

          本文标题:block底层实现

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