Block探究

  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探究)