美文网首页
iOS Block 部分三

iOS Block 部分三

作者: 飞不越疯人院 | 来源:发表于2020-06-30 15:57 被阅读0次

    主要讲解 Block内修改外部变量, 内存管理, 以及循环引用;

    Block部分一
    Block部分二
    Block部分三
    Block知识点总结

    以下内容的测试主要针对ARC环境; MRC下直接贴出测试结果, 不再贴出测试代码, 具体请自行测试MRC环境;

    1. __block的用法(基础类型)

    block内部需要修改外部变量时()
    全局变量, 全局静态变量可以直接进行修改, 因为block内部不对他们进行捕获;
    静态局部变量也可以直接在block内部修改, 因为�block捕获了它的地址;
    auto变量则不能直接在block内部修改; 需要用__block修饰才行;


    先从底层结构代码看下为什么auto变量为什么不能直接在block内部修改;
    首先我们知道int c = 3整个变量的作用域就是viewDidLoad这个方法, 出了这个方法后就不能再访问;
    底层代码如下:
    ///viewDidLoad的底层代码如下
    static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
         int c = 3;
        static int d = 4;
        void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, c, &d));
    
        ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
    }
    

    block内部执行的代码是封装在这个函数中, 从明面即可得知, 这个函数中并不能访问另一个函数的auto变量, 这个函数内部里面的变量c; 是block底层的结构体重新创建的变量 int c = __cself->c;

    static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
      int c = __cself->c; // bound by copy
      int *d = __cself->d; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController3_2637aa_mi_0, a,b,c,(*d));
        }
    

    为什么用__block修饰后的auto变量就可以在block内部修改了呢?

    下面代码, 通过__block修饰后就可以在block内部修改;

    @implementation ViewController3
    - (void)viewDidLoad {
        [super viewDidLoad];
      __block    int XXXX = 3;
        void(^Case3Block)(void) =  ^{
            XXXX = 300;
        };
        NSLog(@"XXXX = %d", XXXX);
        Case3Block();
        NSLog(@"XXXX = %d", XXXX);
    }
    @end
    
    2020-06-27 15:27:21.694208+0800 BlockMore2[31764:1243071] XXXX = 3
    2020-06-27 15:27:21.694316+0800 BlockMore2[31764:1243071] XXXX = 300
    

    底层的结构为

    ///viewDidLoad的底层
    static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
      ///变量XXXX被封装成了一个对象
      __attribute__((__blocks__(byref))) __Block_byref_XXXX_0 XXXX = {(void*)0,(__Block_byref_XXXX_0 *)&XXXX, 0, sizeof(__Block_byref_XXXX_0), 3};
       ///block的底层
     void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, (__Block_byref_XXXX_0 *)&XXXX, 570425344));
         ///NSlog删掉了
        ///block的调用
        ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
         ///NSlog删掉了
    }
    ===>
    ///block的底层结构
    struct __ViewController3__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController3__viewDidLoad_block_desc_0* Desc;
      ///将XXXX变量封装成一个对象
      __Block_byref_XXXX_0 *XXXX; // by ref
      __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, __Block_byref_XXXX_0 *_XXXX, int flags=0) : XXXX(_XXXX->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    ===>
    ///变量XXXX的封装对象, 底层也是一个结构体
    struct __Block_byref_XXXX_0 {
    ///isa指针
      void *__isa;
    ///指向自身的一个指针
    __Block_byref_XXXX_0 *__forwarding;
    ///flags: 他的作用就是传入不同的值进行不同的操作;
     int __flags;
    ///结构体的size
     int __size;
    ///变量XXXX
     int XXXX;
    };
    
    ===>
    ///封了block内部执行代码块的函数
    static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
      __Block_byref_XXXX_0 *XXXX = __cself->XXXX; // bound by ref
            ///通过__forwarding指针访问XXXX变量
            (XXXX->__forwarding->XXXX) = 300;
        }
    ===>
    注意:
    ///block的描述信息的函数发生了变化, 多了两个函数
    static struct __ViewController3__viewDidLoad_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      ///ARC下栈block会被拷贝到堆上, 这个过程会降封装的变量也进行拷贝;
      void (*copy)(struct __ViewController3__viewDidLoad_block_impl_0*, struct __ViewController3__viewDidLoad_block_impl_0*);
    ///当block执行完毕从堆上移出时,会调用dispose函数对所持有的对象进行类似release操作;
      void (*dispose)(struct __ViewController3__viewDidLoad_block_impl_0*);
    } __ViewController3__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController3__viewDidLoad_block_impl_0), __ViewController3__viewDidLoad_block_copy_0, __ViewController3__viewDidLoad_block_dispose_0};
    
    ===>
    ///copy函数
    static void __ViewController3__viewDidLoad_block_copy_0(struct __ViewController3__viewDidLoad_block_impl_0*dst, struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->XXXX, (void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
    ===>
    ///dispose函数
    static void __ViewController3__viewDidLoad_block_dispose_0(struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
    

    总结:当用__block修饰的auto变量(基础类型)使用block的过程为:

    • 首先被__block修饰的变量在底层会被封装成__Block_byref_XXXX_0的对象变量;
    • block被拷贝大堆上时会调用内部的copy函数, copy函数会通过_Block_object_assign函数对封装的对象进行强引用;
    • block执行完/从堆上移出时, 会调用block内部的dispose函数, 其内部调用_Block_object_dispose函数对结构体持有的对象进行类似release的释放操作;

    2. __block的用法(对象类型)

    首先弄清楚下面两个操作的不同之处;为什么第一个会报错第二个不报错呢;



    因为那里的arr操作是访问它的方法或者属性, 并不是修改它; 如果arr = nil也是会报错的;
    首先看下方代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        __block TestObject *obj = [[TestObject alloc] init];
        void(^Case4Block)(void) = ^ {
            obj = nil;
        };
        NSLog(@"obc = %@", obj);
        Case4Block();
        NSLog(@"obc = %@", obj);
    }
    
    2020-06-27 16:29:03.310124+0800 BlockMore2[32512:1277607] obc = <TestObject: 0x600002e5e920>
    2020-06-27 16:29:03.310238+0800 BlockMore2[32512:1277607] obc = (null)
    

    通过指令后获得底层代码

    ///__Block_byref_obj_0封装对象结构
    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*);
    ///对obj强引用
     TestObject *__strong obj;
    };
    ///block结构
    struct __ViewController4__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController4__viewDidLoad_block_desc_0* Desc;
      ///obj对象被封装成__Block_byref_obj_0对象, block对其强引用
      __Block_byref_obj_0 *obj; // by ref
      __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_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;
      }
    };
    

    弱引用代码示例:

        ///弱引用
        TestObject *obj = [[TestObject alloc] init];
        __block   __weak TestObject *weakObj = obj;
        void(^Case4Block)(void) = ^ {
            NSLog(@"weakObj = %@", weakObj);
        };
        NSLog(@"obc = %@", obj);
        Case4Block();
    

    底层代码实现为

    ///将obj封装成__Block_byref_weakObj_0对象
    struct __Block_byref_weakObj_0 {
      void *__isa;
    __Block_byref_weakObj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
    ///对obj对象弱引用
     TestObject *__weak weakObj;
    };
    ///block底层结构
    struct __ViewController4__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController4__viewDidLoad_block_desc_0* Desc;
    ///对obj封装成的__Block_byref_weakObj_0强引用;
      __Block_byref_weakObj_0 *weakObj; // by ref
      __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_weakObj_0 *_weakObj, int flags=0) : weakObj(_weakObj->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    总结:当用__block修饰的对象类型时使用block整个过程为:

    • 首先被__block修饰的变量在底层会被封装成__Block_byref_XXXX_0的对象变量;
    • block被拷贝大堆上时会调用内部的copy函数, copy函数调用_Block_object_assign函数, 会对对象的修饰符__strong, __weak, unsafe_unretained做出相应的操作(强引用或者弱引用);
    • block执行完/从堆上移出时, 会调用block内部的dispose函数, 其内部调用_Block_object_dispose函数对结构体持有的对象进行类似release的释放操作;
    总结: __block在MRC和ARC下的不同
    • MRC下: 自动变量对象都是被浅拷贝, 不会强引用(不会增加引用计数); 因为MRC环境下, block放在栈区, 所以不会对对象强引用, 如果对block进行拷贝到堆区操作则是强引用;
    • ARC下: block访问局部变量后会自动进行拷贝操作到堆区, 所以会自动变量对象封装后的__Block_byref_XXX对象强引用(引用计数会增加);__Block_byref_XXX对变量根据实际的修饰符进行强/弱引用;

    3. 循环引用问题

    首先看下方代码, 我们都知道产生了循环引用, 但是为什么会循环引用, 通过底层代码查看下原因

    #import "ViewController4.h"
    typedef void(^Block)(void);
    @interface ViewController4 ()
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, copy)  Block  Block1;
    @property (nonatomic, copy)  Block  Block2;
    @end
    @implementation ViewController4
    - (void)viewDidLoad {
        [super viewDidLoad];
        ///产生循环引用
        self.Block1 =    ^{
            NSLog(@"name = %@", self.name);
        };
         self.Block1();
        ///weak 修饰不产生循环引用
        __weak typeof(self) weakSelf = self;
        self.Block2 =    ^{
            NSLog(@"name = %@", weakSelf.name);
        };
        self.Block2();
    }
    @end
    

    底层代码:

    #Block0的底层结构
    struct __ViewController4__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController4__viewDidLoad_block_desc_0* Desc;
      ///对 self 进行强引用
      ViewController4 *const __strong self;
      __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, ViewController4 *const __strong _self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    #Block1的底层结构
    struct __ViewController4__viewDidLoad_block_impl_1 {
      struct __block_impl impl;
      struct __ViewController4__viewDidLoad_block_desc_1* Desc;
      ///对 self 进行弱引用
      ViewController4 *const __weak weakSelf;
      __ViewController4__viewDidLoad_block_impl_1(void *fp, struct __ViewController4__viewDidLoad_block_desc_1 *desc, ViewController4 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    引用block结构中对self指针有强引用, 而self又对block有强引用, 所以造成循环引用;

    ARC解决循环引用方案:

    • __weak修饰, 这样block底层对self的引用变成了XXX *__weak weakSelf; 推荐使用;
    • __unsafe_unretained修饰, 具体效果跟__weak类似, 但是需要注意__unsafe_unretained不会将使用完的对象置为nil; 不推荐使用;

    MRC解决循环引用方案:

    • __block修饰, 将对象封装为__Block_byref_XX_0形式, bock__Block_byref_XX_0是强引用, 但是__Block_byref_XX_0对其结构体内的变量不是强引用;
    • __unsafe_unretained修饰, 具体效果跟__weak类似, 但是需要注意__unsafe_unretained不会将使用完的对象置为nil;


    参考文章和下载链接
    文中测试代码
    iOS clang指令报错问题总结
    Apple 一些源码的下载地址
    auto关键字是什么
    C++中结构体的构造函数
    全局变量、静态全局变量、静态局部变量和普通局部变量的区别

    相关文章

      网友评论

          本文标题:iOS Block 部分三

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