美文网首页
Block探究

Block探究

作者: Maji1 | 来源:发表于2020-11-05 15:00 被阅读0次
    1. block的实质是什么?
    2. 一共有几种block?
    3. 都是什么情况下生成的?

    block的实质是什么?

    • block本质上也是一个OC对象,它内部也有个isa指针
    • block是封装了函数调用以及函数调用环境的OC对象
    • block是封装函数及其上下文的OC对象

    如何查看block源码:

    • 打开终端,在main.m所在目录下键入clang -rewrite-objc main.m即可在当前目录下生成一个main.cpp文件;
    • 当引用了OC中的Foundation或者UIKit框架时,通过 clang -rewrite-objc 指定文件名 命令将指定文件转换成C++代码会报错;
    • 可通过 clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 指定文件名

    先写一段简单的block代码:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int num = 10;
            void(^block)(int ,int) = ^(int a, int b){
                NSLog(@"a = %d,b = %d",a,b);
                NSLog(@"num = %d",num);
            };
            block(1,2);
        }
        return 0;
    } 
    

    转化为c++源码:

    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int num = 10;
            void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
        }
        return 0;
    }
    

    对比两段代码,发现定义block的源码:
    void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
    
    • 调用__main_block_impl_0函数,并且将函数地址赋值给block
    • 传了三个参数(void *)__main_block_func_0&__main_block_desc_0_DATAnum

    __main_block_impl_0结构体:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int num;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • __main_block_impl_0结构体内有一个同名构造函数__main_block_impl_0,构造函数中带有四个参数。
    • 参数1*fp:对应定义时传过来的(void *)__main_block_func_0函数地址,由__block_impl impl的属性FuncPtr接受。
      注意⚠️:该参数记录的是block内代码块的地址。
    • 参数2desc:对应定义时传过来的&__main_block_desc_0_DATA地址,由__main_block_desc_0* Desc接受。
      注意⚠️:该参数记录着block对象占用内存的大小。
    • 参数3_num : 对应定义时传过来的num
    • 参数4flags:默认值0.

    定义时将__main_block_impl_0结构体的地址赋值给了block


    参数1:(void *)__main_block_func_0
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
      int num = __cself->num; // bound by copy
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_1c8b6c_mi_0,a,b);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_1c8b6c_mi_1,num);
    }
    
    • 该函数中首先取出了num的值。然后就是两个NSLog,就是我们block代码块中的打印。所以我们断定,block代码块中写下的代码被封装成了__main_block_func_0函数。函数地址由__main_block_impl_0结构体中的 __block_impl的属性FuncPtr保存。
    参数2:&__main_block_desc_0_DATA
    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)};
    
    • 参数1 reserved:值默认为0。
    • 参数2 Block_size:默认值为sizeof(struct __main_block_impl_0),也就是 __main_block_impl_0结构体占用空间的大小。
    参数3:_num

    我们定义的局部变量。因为在block块内用到了变量num,所以block在声明的时候会将num作为参数传入,捕获参数num
    如果在block块内没有使用到num,就不会作为参数传入。

    注意⚠️:这里就是为什么在定义block之后修改局部变量的值,再调用block,修改的值无法生效的原因。
    定义block时已经将局部变量的值传入__main_block_impl_0结构体中,调用block时直接从__main_block_impl_0结构体中将值取出来。



    __block_impl结构体
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    • isa指针:存储着&_NSConcreteStackBlock地址(理解为类对象地址)。block就是_NSConcreteStackBlock类型的。
    • FuncPtr 函数地址:存储着__main_block_func_0函数的地址。也就是block内代码块的地址。

    该结构体内含有isa指针,因此可以证明block本质上就是一个OC对象。


    调用block(1,2);的源码:
     ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
    
    • 直接将block转化为__block_impl类型,取出__main_block_func_0函数的地址FuncPtr(也就是block内代码块的地址)。
    • block本身 和 值12传过去。

    注意⚠️:block__main_block_impl_0类型的结构体,怎么可以直接强转为__block_impl类型?

    因为__block_impl__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。


    block捕获变量

    我们修改下代码:

    int globalNum = 30;
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int num = 10;
            static int staticNum = 20;
            void(^block)(int ,int) = ^(int a, int b){
                NSLog(@"a = %d,b = %d",a,b);
                NSLog(@"num = %d, count = %d, globalCount = %d",num, staticNum, globalNum);
            };
            num = 5;
            staticNum = 15;
            block(1,2);
        }
        return 0;
    }
    

    再看下源码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int num;
      int *staticNum;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int *_staticNum, int flags=0) : num(_num), staticNum(_staticNum) {
        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, int b) {
      int num = __cself->num; // bound by copy
      int *staticNum = __cself->staticNum; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_c17f7e_mi_0,a,b);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_c17f7e_mi_1,num, (*staticNum), globalNum);
            }
    
    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(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int num = 10;
            static int staticNum = 20;
            void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num, &staticNum));
            num = 5;
            staticNum = 15;
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
        }
        return 0;
    }
    

    可以看出,num是值传递,staticNum是指针传递& staticNumglobalNum没有传递,而是直接访问。

    1、局部变量-自动变量(auto变量)

    局部变量前面自动添加auto关键字,auto只存在于局部变量中,离开作用域就销毁。

    上述代码中已经验证,自动变量会捕获到block内部,block内部会专门新增加一个参数来存储变量的值。访问方式为 值传递

    2、局部变量-静态变量(static变量)

    static修饰的变量同样会被block捕获,访问方式为 指针传递

    局部变量可能会销毁,调用block时如果该变量被销毁了,就不能访问该变量的地址,所以只能传递值。静态变量不会被销毁,所以可以传地址,传地址不回增加内存的消耗。
    所以,在block调用之前修改地址中保存的值,block中的地址是不会变的。所以值会随之改变。

    3、全局变量
    不会被block捕获,不用传递,直接访问。


    block内使用self

    OC代码:

    @implementation CQTest
    
    - (void)testDemo1 {
        void(^block)(void) = ^{
            NSLog(@"%@",self);
        };
        block();
    }
    + (void)testDemo2 {
        
    }
    @end
    

    C++代码:

    struct __CQTest__testDemo1_block_impl_0 {
      struct __block_impl impl;
      struct __CQTest__testDemo1_block_desc_0* Desc;
      CQTest *self;
      __CQTest__testDemo1_block_impl_0(void *fp, struct __CQTest__testDemo1_block_desc_0 *desc, CQTest *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    ......
    static void _I_CQTest_testDemo1(CQTest * self, SEL _cmd) {
        void(*block)(void) = ((void (*)())&__CQTest__testDemo1_block_impl_0((void *)__CQTest__testDemo1_block_func_0, &__CQTest__testDemo1_block_desc_0_DATA, self, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    static void _C_CQTest_testDemo2(Class self, SEL _cmd) {
    
    }
    
    • block内调用selfCQTest *self;被捕获。
    • 对象方法 testDemo1 和类方法 testDemo2 都传递了 self 和 方法选择器 _cmd

    对象方法类方法 都会默认将self作为参数传递给方法内部,所以 self局部变量。前面已经验证 局部变量 才会被 block 捕获。


    block内使用 成员变量实例属性 的区别

    OC代码:

    - (void)testDemo1 {
        void(^block)(void) = ^{
            NSLog(@"self.num = %@",self.num);
            NSLog(@"_num = %@",self->_num);
        };
        block();
    }
    

    C++代码:

    struct __CQTest__testDemo1_block_impl_0 {
      struct __block_impl impl;
      struct __CQTest__testDemo1_block_desc_0* Desc;
      CQTest *self;
      __CQTest__testDemo1_block_impl_0(void *fp, struct __CQTest__testDemo1_block_desc_0 *desc, CQTest *_self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __CQTest__testDemo1_block_func_0(struct __CQTest__testDemo1_block_impl_0 *__cself) {
      CQTest *self = __cself->self; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_CQTest_4b6f9e_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("num")));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_CQTest_4b6f9e_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_CQTest$_num)));
        }
    
    • 只捕获了CQTest *self;
    • self.num:调用了get方法,通过方法选择器获取属性的值。
    • _num:直接通过地址获取值。

    一共有几种block?每种类型都是什么情况下生成的?

    打印看下block的类型:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            void(^block)(int ,int) = ^(int a, int b){
            };
            NSLog(@"\n %@ \n %@ \n %@ \n %@ \n",
                  [block class],
                  [[block class] superclass],
                  [[[block class] superclass] superclass],
                  [[[[block class] superclass] superclass] superclass]);
        }
        return 0;
    }
    

    输出日志:

     __NSGlobalBlock__ 
     __NSGlobalBlock 
     NSBlock 
     NSObject
    

    这里打印的是__NSGlobalBlock类型,继承之NSBlock,但是最终还是继承之NSObject,再次证明blockOC对象。

    前面的代码中我们看到impl.isa 指向的都是 _NSConcreteStackBlock 类对象地址。其实block的类型共三种:

    • _NSConcreteGlobalBlock 全局静态。
    • _NSConcreteStackBlock 保存在栈中。
    • _NSConcreteMallocBlock 保存在堆中。
    都是什么情况下生成的?

    看段代码:

    void (^block1)(void) = ^{
      NSLog(@"block1");
    };
    int a = 10;
    void (^block2)(void) = ^{
       NSLog(@"block2-%d",a);
    };
    NSLog(@"\n block1:%@ \n block2:%@ \n block3:%@ \n",
      [block1 class],
      [block2 class],
      [^{
        NSLog(@"block3-%d",a);
    } class]);
    
    • block1:内部没有调用外部变量。
    • block2:内部调用外部变量。
    • block3:内部调用外部变量,直接调用的block的class。

    看下书输出日志:

     block1:__NSGlobalBlock__ 
     block2:__NSMallocBlock__ 
     block3:__NSStackBlock__
    

    block在内存中六大区域的位置
    类型 描述 对应block类型
    存储局部变量,当其作用域执行完毕之后,就会被系统立即收回 NSStackBlock
    存储OC对象,手动申请的字节空间,需要手动释放 NSMallocBlock
    BSS段 未初始化的全局变量和静态变量,一旦初始化就会从BSS段中回收掉,转存到数据段中
    数据段 存储已经初始化的全局变量和静态变量,以及常量数据,直到结束程序时才会被立即收回 NSGlobalBlock
    常量区 存放常量字符串,程序结束后由系统释放
    代码段 存放函数的二进制代码,内存区域较小,直到结束程序时才会被立即收回
    • __NSGlobalBlock__:存放在数据段,直到程序结束才会被回收,不过我们很少使用。
    • __NSStackBlock__:存放在栈区,由系统自动分配和释放,作用域执行完毕之后就会被立即释放。很少使用。
    • __NSMallocBlock__:存放在堆区,最常使用,存放在堆中需要我们自己进行内存管理。

    __NSMallocBlock__调用了copy之后不会改变类型。
    __NSStackBlock__调用了copy之后就会变成__NSMallocBlock__类型。
    __NSMallocBlock__调用了copy之后引用计数会增加。

    所以,在 MRC 环境下开发时,经常需要使用copyblock拷贝到堆中。即使栈上的block被销毁,堆上的block也不会被销毁,需要我们自己调用release操作来销毁。
    而在ARC环境下系统会自动copyblock不会被销毁。

    我们在ARC环境下定义全局的block属性时经常使用copy关键字,这是沿用了 MRC 环境下的书写风格,其实在ARC环境下使用copystrong关键字是一样的。


    ARC中在什么情况下系统会自动将block进行一次copy操作?
      1. block作为函数的返回值时。
      1. block被强指针引用时。
      1. block作为Cocoa API中方法名含有usingBlock的方法参数时。
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {  }]
    
      1. block作为GCD API的方法参数时。
    dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
            dispatch_block_t block);
    

    block如何捕获 OC 对象?

    前面我们捕获的除了self都是基本数据类型,下面研究下捕获OC对象的方式。

    ARC环境下代码:

    typedef void(^Block)(void);
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                CQTest *test = [[CQTest alloc] init];
                test.num = @"123";
                block = ^{
                    NSLog(@"%@", test.num);
                };
                NSLog(@"%@", [block class]);//输出__NSMallocBlock__
            }//test不会被释放
        }//test被释放
        return 0;
    }
    
    • 这里的block类型是__NSMallocBlock__,存在堆区。

    C++代码

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      CQTest *test;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CQTest *_test, int flags=0) : test(_test) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • CQTest *test;看到了这句代码,说明强引用了 test

    ARC下,test在使用后并不会被立即释放。因为block代码块内了强引用test,系统会对block自动copyblock存到堆区。

    上述代码如果在MRCtest就会被提前释放。因为这时的block存在栈区,不会强引用test

    __weak弱引用test:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            Block block;
            {
                CQTest *test = [[CQTest alloc] init];
                test.num = @"123";
                __weak CQTest *weakTest = test;
                block = ^{
                    NSLog(@"%@", weakTest.num);
                };
                NSLog(@"%@", [block class]);
            }//test会被释放
        }
        return 0;
    }
    

    __weak修饰变量,需要告知编译器使用ARC环境及版本号否则会报错,添加说明-fobjc-arc -fobjc-runtime=ios-8.0.0

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

    C++代码:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      CQTest *__weak weakTest;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, CQTest *__weak _weakTest, int flags=0) : weakTest(_weakTest) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      CQTest *__weak weakTest = __cself->weakTest; // bound by copy
    
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_415682_mi_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakTest, sel_registerName("num")));
    }
    
    • CQTest *__weak weakTest;看到weakTest为弱引用。
    • test在作用域结束后被销毁。

    再继续往下看C++代码:

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakTest, (void*)src->weakTest, 3/*BLOCK_FIELD_IS_OBJECT*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakTest, 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};
    
    • 我们发现多了两个函数
      1、__main_block_copy_0 :内部调用了_Block_object_assign函数,并且传递了原weakTest地址 和 目标weakTest地址 。
      2、 __main_block_dispose_0:内部带哦用了_Block_object_dispose函数,传递了原weakTest地址。
    • 看到copydispose 中传递的都是block结构体本身 __main_block_impl_0

    _Block_object_assign调用时机及作用:

    block进行copy操作的时候会自动调用__main_block_desc_0内部的__main_block_copy_0函数,__main_block_copy_0函数内部再调用_Block_object_assign函数。

    _Block_object_assign函数会自动根据__main_block_impl_0结构体内部对象的指针类型,对对象产生 强引用 还是 弱引用
    可以理解为_Block_object_assign函数内部会对对象test进行引用计数器的操作,如果__main_block_impl_0结构体内test指针是__strong类型,则为强引用,引用计数+1,如果指针是__weak类型,则为弱引用,引用计数不变。

    _Block_object_dispose调用时机及作用:

    block从堆中移除时就会自动调用__main_block_desc_0中的__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数。

    _Block_object_dispose会对对象做释放操作,类似于release,也就是断开对对象的引用,而对象是否被释放还是取决于对象自己的引用计数。


    总结:

    1、block捕获的变量为对象时,__main_block_desc_0结构体中会出现像个参数copydisposeblock希望对捕获的对象进行内存管理。
    2、block捕获的对象为auto时,如果block存储在栈区(此种情况为MRC下),不会对捕获的对象强引用。
    3、一旦 block 被拷贝到堆上,copy函数会调用_Block_object_assign函数,根据捕获对象的指针类型(__strong,__weak,unsafe_unretained)进行 强引用 或者 弱引用
    4、一旦 block 从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。


    __block的作用

    __block用于解决block内部不能修改auto变量值的问题,__block不能修饰 静态变量(static) 和 全局变量。

    OC代码:

     __block int num = 5;
            Block  block = ^{
                num = 6;
                NSLog(@"%d", num);
    };
    

    C++代码:

    
    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};
            Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_dfce68_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    
        }
        return 0;
    }
    
    struct __Block_byref_num_0 {
      void *__isa;
    __Block_byref_num_0 *__forwarding;
     int __flags;
     int __size;
     int num;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_num_0 *num; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 自动生成了__Block_byref_num_0类型的结构体。
      1、__isa__Block_byref_num_0本质上也是一个对象。
      2、__forwarding:也是__Block_byref_num_0类型,存储的结构体自己的内存地址。
      3、__size__Block_byref_num_0所占用的内存空间。
      4、num:真正存储的变量地方。
    • __main_block_impl_0结构体中并没有直接存储整型num,而是储着__Block_byref_num_0类型的结构体num
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_num_0 *num = __cself->num; // bound by ref
    
                (num->__forwarding->num) = 6;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hx_3p3kcn8d1dqf0225mh9pb2vr0000gn_T_main_25b6a2_mi_0, (num->__forwarding->num));
    }
    
    • num->__forwarding->num:通过结构体num(__Block_byref_num_0类型)拿到自身的地址__forwarding(为了方便内存管理),再拿到我们在block外面定义的变量num

    __block将变量包装成一个结构体对象,然后再把变量存储在结构体里面。block内部存储对象指针,所以可以通过指针找到内存地址修改变量的值。

    __block修饰对象:
    __block  CQTest *test = [[CQTest alloc] init];
    Block  block = ^{
        NSLog(@"%@", test.num);
    };
    block();
    

    C++代码:

    struct __Block_byref_test_0 {// 48 共占用内存空间
      void *__isa; // 8 内存空间
    __Block_byref_test_0 *__forwarding; // 8 内存空间
     int __flags; // 4 内存空间
     int __size; // 4 内存空间
     void (*__Block_byref_id_object_copy)(void*, void*); // 8 内存空间
     void (*__Block_byref_id_object_dispose)(void*); // 8 内存空间
     CQTest *test; // 8 内存空间
    };
    
    __attribute__((__blocks__(byref))) __Block_byref_test_0 test = {(void*)0,(__Block_byref_test_0 *)&test, 33554432, sizeof(__Block_byref_test_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((CQTest *(*)(id, SEL))(void *)objc_msgSend)((id)((CQTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CQTest"), sel_registerName("alloc")), sel_registerName("init"))};
            Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_test_0 *)&test, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    • 1、同样生成了一个结构体。并且结构体内部存储了对象test
    • 2、多了两个函数__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131
    • 3、__Block_byref_test_0占用的内存空间为48。src加40恰好指向的就为test指针。
    • 4、_Block_object_assign函数传入的是test地址
    __block__weak同时修饰对象:

    OC代码:

    CQTest *test = [[CQTest alloc] init];
    __block __weak CQTest *weakTest = test;
    Block  block = ^{
        NSLog(@"%@", weakTest.num);
    };
    block();
    

    C++代码:

    struct __Block_byref_weakTest_0 {
      void *__isa;
    __Block_byref_weakTest_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     CQTest *__weak weakTest;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_weakTest_0 *weakTest; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakTest_0 *_weakTest, int flags=0) : weakTest(_weakTest->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 系统自动生成的结构体__Block_byref_weakTest_0依然是强引用。
    • 结构体__Block_byref_weakTest_0对内部的weakTest为弱引用。

    但是在mrc环境下,尽管调用copy操作,__block结构体不会对test产生强引用,依然是弱引用。

    __block修饰的变量内存管理:

    1、当block内存在栈上时,并不会对__block变量产生内存管理。
    blcokcopy到堆上时会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)。

    2、当blockcopy到堆上时,block内部引用的__block变量也会被复制到堆上,并且持有变量,如果block复制到堆上的同时,__block变量已经存在堆上了,则不会复制。
    而此时栈中的__Block_byref_test_0结构体中的__forwarding指针指向的就是堆中的__Block_byref_test_0结构体,堆中__Block_byref_test_0结构体内的__forwarding指针依然指向自己。

    3、当block从堆中移除的时,就会调用__main_block_dispose_0函数,__main_block_dispose_0函数内部会调用_Block_object_dispose函数,会自动释放引用的__block变量。

    4、一旦使用__block修饰的变量,__main_block_impl_0结构体内一律使用强指针引用生成的结构体。
    对结构体内部变量的引用取决于我们在外部定义变量时的指针类型。


    block循环引用问题

    ARC环境下:
    CQTest *test = [[CQTest alloc] init];
    test.block = ^{
        NSLog(@"%@", test.num);
    };
    
    • testblock之间相互强引用,都不会被释放,内存泄漏。

    解决方式:
    1、使用__weak__unsafe_unretained修饰符可以解决循环引用的问题。

    • __weak不会产生强引用,指向的对象销毁时,会自动将指针置为nil。因此一般通过__weak来解决问题。

    • __unsafe_unretained不会产生前引用,不安全,指向的对象销毁时,指针存储的地址值不变。

    2、__block修饰符也可以解决循环引用的问题。

     __block CQTest *test = [[CQTest alloc] init];
    test.block = ^{
        NSLog(@"%@", test.num);
        test = nil;
    };
    test.block();
    
    • __block修饰变量会自动生成一个结构体__Block_byref_test_0
    • test->block->__Block_byref_test_0->test三个对象形成了循环强引用。
    • test.block();调用后test被设置为nil__Block_byref_test_0也就断开了对test的强引用,循环引用被断开,都可以被正常释放了。

    __block解决循环引用的条件:1、必须执行block()。 2、block代码块内必须将test设置为nil

    MRC环境下:

    1、可通过__unsafe_unretained来解决问题,但是使用的问题跟ARC下相同。__weakMRC下不能用。
    2、使用__block来解决。在MRCblock即使手动调用了copy,自动生成的结构体对test依然是弱引用。所以可以解决循环引用的问题。

    __strong__weak
     __weak typeof(self) weakSelf = self;
    test.block = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
      NSLog(@"%@", strongSelf.num);
    };
    
    • block内部重新使用__strong修饰self变量是为了在block内部有一个强指针指向weakSelf避免在block调用的时候weakSelf已经被销毁。

    相关文章

      网友评论

          本文标题:Block探究

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