美文网首页
iOS Block基本使用(三)

iOS Block基本使用(三)

作者: 学不来的凡人 | 来源:发表于2021-03-18 09:57 被阅读0次

    问题:block内部捕获到的局部变量是局部变量的值,所以在block上修改不了局部变量。
    但是局部的静态变量捕获是指针,所以block内部可以修改值。全局变量和全局静态变量没有捕获,可以直接获取到(不存在作用域的问题)
    怎么在block内部修改局部变量???
    方法一:改成静态变量!!!
    方法二:__block修饰局部变量

    void test(){
        __block int a = 10;
        MyBlock myBlock = ^{
            a = 11;
            NSLog(@"myBlock---------%d", a);//11
        };
        myBlock();
    }
    

    转成底层代码

    typedef void(*MyBlock)(void);
    //在block内部捕获的时候把&a传进来包装成了__Block_byref_a_0对象
    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
            (a->__forwarding->a) = 11;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_cd5b88_mi_0, (a->__forwarding->a));
        }
    static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
      void (*dispose)(struct __test_block_impl_0*);
    } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
    void test(){
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    

    可以看到__block修饰的局部变量a,在block内部捕获的时候 变成了一个 __Block_byref_a_0 *a; // by ref 类型的对象 Blcok内部有个指向这个对象的指针 而这个对象拥有a变量(而且这个对象拥有的内存地址和我们声明的a的内存地址是一样的) 被执行Block的时候 通过被包装对象的__forwarding对象 访问到a 进行修改


    image.png

    __block的内存管理

    当block在栈上的时候 并不会对__block变量产生强引用(ARC)或者retain操作(MRC)

    当block被拷贝到堆上时:
    1.会调用Block内部copy函数 这个函数内部会调用_Block_object_assign函数
    2._Block_object_assign函数会对__block变量形成强引用(retain) (__block修饰的变量也会被拷贝到堆上)
    3.只要是被__block修饰的变量 copy后都是强引用 如果没被__block修饰的对象 会根据对象的修饰符来确定是强引用还是弱引用

    当Block从堆中移除的时候:
    1.会调用Block内部的dispose函数 这个函数内部会调用_Block_object_dispose函数
    2._Block_object_dispose 这个函数会断开对__block修饰的变量的强引用(realse)
    block内部使用了被__block修饰的对象的话 那么block内部的 Desc 描述指针也会多两个函数 这两个函数就是用于内存管理的copy函数和dispose函数

    不使用__block修饰对象类型

    void test(){
        NSObject *objc = [NSObject new];
        MyBlock myBlock = ^{
            NSLog(@"myBlock---------%@", objc);//11
        };
        myBlock();
    }
    
    typedef void(*MyBlock)(void);
    
    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      NSObject *objc;
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      NSObject *objc = __cself->objc; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_b0ec24_mi_0, objc);
        }
    static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static struct __test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
      void (*dispose)(struct __test_block_impl_0*);
    } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
    void test(){
        NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, objc, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    

    __block修饰对象类型

    void test(){
        __block NSObject *objc = [NSObject new];
        MyBlock myBlock = ^{
            NSLog(@"myBlock---------%@", objc);//11
        };
        myBlock();
    }
    

    转成底层代码

    typedef void(*MyBlock)(void);
    struct __Block_byref_objc_0 {
      void *__isa;
    __Block_byref_objc_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     NSObject *objc;
    };
    
    struct __test_block_impl_0 {
      struct __block_impl impl;
      struct __test_block_desc_0* Desc;
      __Block_byref_objc_0 *objc; // by ref
      __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
      __Block_byref_objc_0 *objc = __cself->objc; // bound by ref
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_c53779_mi_0, (objc->__forwarding->objc));
        }
    static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
      void (*dispose)(struct __test_block_impl_0*);
    } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
    void test(){
        __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {(void*)0,(__Block_byref_objc_0 *)&objc, 33554432, sizeof(__Block_byref_objc_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
        MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    

    可以看到和__block修饰的普通变量差不多 但是包装成的对象多了两个函数 copy函数和dispose函数 这两个和Block描述中的copy函数和dispose函数还不一样,当Block拷贝的到堆上的时候 被__block修饰的对象(objc)转化的对象(__Block_byref_objc_0)也会被拷贝到堆上,这个时候会触发被包装对象的copy函数 而这个函数会根据外部__block修饰对象(objc)的引用计数修饰符来决定对外部对象(objc)是强引用还是弱引用。当Block从堆中移除的时候 会调用内部的dispose函数 这个函数会断开对转化对象(__Block_byref_objc_0)的引用关系 如果计数为0 就直接释放 释放后会调用自己的dispose函数 对自己内部对象(objc) 断开引用关系 如果此时objc的引用计数为0 就会被释放掉。(转化对象持有的objc和我们外部声明的objc对象有同一片内存地址 是同一个对象)

    注意:上面的分析 都是ARC的情况下。如果是MRC转化对象(_Block_byref objc_0) 对外部对象(objc) 一直都是弱引用 不管是什么情况。这也是为什么在MRC下我们使用__block来解决循环引用的原因

    Block与循环引用
    1.在ARC的情况下 我们常常使用__weak __unsafe__unretained来解决

    我们使用__weak 和 __unsafe__unretained 都有一个共同的作用 就是使Block持有的对象指向我们声明的对象的指针式弱引用,那么我们外部指向实例对象的指针一旦被释放 实例对象就会被释放 那么Block就会被释放 从而解决循环引用。区别就是如果我们使用的是__weak 一旦我们的实例对象被释放,block内部持有的指向我们实例对象的指针会被指nil 不会产生野指针,但是__unsafe__unretained 不会有这一步操作 所有如果使用__unsafe__unretained block内部持有的指向我们实例对象的指针会还会指向我们已经释放的实例对象的地址 产生野指针。这也是它不安全的原因。所以我们一般使用__weak来解决循环引用

    2.在MRC的情况下 我们使用__unsafe__unretained __block来解决 因为不支持weak指针

    这两个都可以使Block内部的指向我们实例对象的指针进行retain操作

    1.Block的原理是怎样的?本质是什么?

    Block是封装了函数调用以及函数调用环境的OC对象 本质上也是一个OC对象

    Block内部包含了一个__block_imp1 这个指针内部包含了一个isa指针(也证明了本质是个对象) 和 block内部封装的代码块的执行地址 以及捕获的变量的指针

    和一个__main_block_desc_0 里面包含了block的大小等 如果内部使用了实例对象或者__block修饰的变量 会包含一个copy函数和dispose函数.

    2.__block的作用是什么?有什么使用注意点?

    被__block修饰的变量 在block内部可以被改变,因为在block内部被__block修饰的变量会被包装成一个__Block_byref_xxx_0的对象,这个对象内部有一个指向我们声明的变量的指针 指向的地址和我们声明的变量是同一个地址,修改的时候也是通过这个对象拿到指针做修改。block内部对这个对象是强引用的。而如果被修饰的变量是实例对象 那么这个对象内部会添加一个copy函数和dispose函数,在被copy到堆上时,这个对象的copy函数会根据被修饰实例对象的关键字来确定对实例对象是强引用还是弱引用。再block释放的时候,会调用dispose函数 来断开对实例对象的引用关系。需要注意的是__block只能修饰实例对象 还有一些内存管理问题 比如说修饰实例对象且该实例对象拥有该Block 会造成循环引用。而且在MRC下使用__block修饰的实例对象 不会被__Block_byref_xxx_0的对象retain操作。对象可能会被提前释放。

    3.Block的属性修饰词为什么是copy? 使用Block有哪些注意?

    如果block不被copy 就是存储在栈空间上,我们就控制不了block的生命周期,可能我们使用的时候已经被释放了或者我们使用的时候 其内部捕获的变量已经释放了 导致程序错误。而拷贝到堆上,我们可以方便的控制其生命周期。虽然增加了管理内存的一些成本。但是可以减少错误。在ARC的情况下 如果有一个强引用指向Block 内部也会copy到堆上。使用strong也行。但是我们习惯使用copy。 需要注意的是不能引起循环引用。我们可以使用__weak来避免这个问题

    4.Block修改NSMutableArray 需不需要添加__block?

    不需要,如果是对数组进行增删改查 我们不需要对其添加__block 因为不是改变数组的指针指向 只是操作数组。添加__block反而会增加开销。

    Block的copy

    _NSConcreteStackBlock copy 变成 _NSConcreteMallocBlock
    _NSConcreteGlobalBlock copy 还是全局block 什么也不做
    _NSConcreteMallocBlock copy 引用计数加1

    在ARC环境下 编译器会根据情况自动将栈上的Block复制到堆上 相当于对栈上的Block进行copy操作。
    1.Blcok 作为函数返回值的时候 会自动copy
    2.将Block赋值给__strong强指针的时候 也会自动做copy操作
    3.Block作为GCD的参数时 也会被copy到堆上
    4.Foundation框架下 block作为参数且方法名含有usingBlock时 会被自动copy

    相关文章

      网友评论

          本文标题:iOS Block基本使用(三)

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