经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(四)block
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:50:11  对本文有异议

block的本质

1.block的基本用法

  1. // 不带参数无返回值的block
  2. void (^block)(void) = ^{
  3. NSLog(@"Hello, World!");
  4. };
  5. block();
  6. // 带参数无返回值的block
  7. void (^block)(int, int) = ^(int a , int b)
  8. NSLog(@"this is a block!");
  9. };
  10. block(10, 20);

2.将block代码转换成C++文件后发现,生成了一个__main_block_impl_0类型的结构体,block是指向这个结构体的指针

  1. int age = 20;
  2. void (^block)(int, int) = ^(int a , int b){
  3. NSLog(@"this is a block! -- %d", age);
  4. NSLog(@"this is a block!");
  5. NSLog(@"this is a block!");
  6. NSLog(@"this is a block!");
  7. };
  8. block(10, 10);
  9. // 转换后的C++代码
  10. int main(int argc, const char * argv[]) {
  11. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  12. int age = 10;
  13. // 定义block变量
  14. // block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age)
  15. void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
  16. // 执行block内部代码
  17. // __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
  18. // (block->FuncPtr)(block, 10, 10)
  19. ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
  20. }
  21. return 0;
  22. }

3.__main_block_impl_0类型的结构体,里面包含了__block_impl类型的结构体变量impl__main_block_desc_0类型的结构体变量Desc,一个返回值为__main_block_impl_0类型的构造函数,还会生成一个age来存储外面引用的值

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. int age;
  5. // 构造函数(类似oc的init)
  6. // __main_block_func_0的地址传给fp
  7. // : age(_age)语法会自动将_ag赋值给age
  8. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
  9. impl.isa = &_NSConcreteStackBlock; // isa指向的_NSConcreteStackBlock就是当前block类型
  10. impl.Flags = flags;
  11. impl.FuncPtr = fp; // 保存的就是__main_block_func_0的地址,也就是block执行逻辑的函数
  12. Desc = desc; // 保存的是&__main_block_desc_0_DATA的地址(主要存储的就是__main_block_impl_0的大小)
  13. // 默认返回的就是__main_block_impl_0结构体
  14. }
  15. };

4.__block_impl类型的结构体里包含isa指针,说明block也是一个OC对象。在__main_block_impl_0的构造函数中isa指向的是_NSConcreteStackBlock类型的地址,侧面说明这个类型也是当前编译时的block的真实类型

  1. struct __block_impl {
  2. void *isa;
  3. int Flags;
  4. int Reserved;
  5. void *FuncPtr;
  6. };

5.__main_block_func_0是一个封装了block执行逻辑代码的函数,在__main_block_impl_0的构造函数中通过参数void *fp赋值给FuncPtr指针变量,来保存执行代码的地址

  1. // 封装了block执行逻辑的函数
  2. static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  3. int age = __cself->age; // bound by copy
  4. NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
  5. NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1);
  6. NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2);
  7. NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);
  8. }

6.__main_block_desc_0这个类型的结构体变量__main_block_desc_0_DATA里面的reserved赋值为0,Block_size赋值为sizeof(struct __main_block_impl_0),也就是当前__main_block_impl_0这个结构体的大小。在__main_block_impl_0的构造函数中通过参数desc赋值给Desc

  1. static struct __main_block_desc_0 {
  2. size_t reserved; // 0
  3. size_t Block_size; // 计算的就是__main_block_impl_0这个结构体的大小
  4. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

7.__main_block_impl_0的构造函数中 : age(_age)语法会自动将_age赋值给变量int age来保存。而且转换后的调用__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0

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

block的本质结构可以概括为下面这张图

block的变量捕获

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

  • 局部变量默认是被auto修饰的,表示自动变量,离开作用域就销毁。block的捕获该变量是值传递
  • 局部变量被static修饰,会一直在内存中不被释放,block的捕获该变量是指针传递
  • 全局变量因为是一直都在内存中存在的,所以不用捕获

block的类型

  • block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
    • __NSGlobalBlock__ ( _NSConcreteGlobalBlock ) - __NSStackBlock__ ( _NSConcreteStackBlock ) - __NSMallocBlock__ ( _NSConcreteMallocBlock)
  • block的真实类型都是以运行时为准的,通过Clang编译出的C++类型不是最准确的,因为在运行时又会做了一些变动和处理。而且现在LLVM只会生成一种中间文件,和Clang生成的文件有差异

通过下面代码观察block的对应输出

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. void (^block)(void) = ^{
  4. NSLog(@"Hello");
  5. };
  6. NSLog(@"%@", [block class]);
  7. NSLog(@"%@", [[block class] superclass]);
  8. NSLog(@"%@", [[[block class] superclass] superclass]);
  9. }
  10. return 0;
  11. }
  12. // 对应的输出:__NSGlobalBlock__ NSBlock NSObject

不同内存区域对应的block类型不同

  • 数据段对应的是__NSGlobalBlock__类型的block
  • 堆段对应的是__NSMallocBlock__类型的block
  • 栈段对应的是__NSStackBlock__类型的block

不同操作对应的block类型不同

  • 没有访问自动变量的block的类型是__NSGlobalBlock__
  • 访问了自动变量的block的类型是__NSStackBlock__
  • __NSStackBlock__的block调用了copy后类型会变为__NSMallocBlock__

每一种类型的block调用copy后的结果如下所示

修改Xcode的Build Setting->Objective-C Automatic Reference CountingNo,使编译环境为MRC,然后输出下面代码可以查看block对应的类型

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. int a = 10;
  4. // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
  5. void (^block1)(void) = ^{
  6. NSLog(@"Hello");
  7. };
  8. int age = 10;
  9. void (^block2)(void) = ^{
  10. NSLog(@"Hello - %d", age);
  11. };
  12. NSLog(@"%@ %@ %@", [block1 class], [[block2 copy] class], [^{
  13. NSLog(@"%d", age);
  14. } class]);
  15. }
  16. return 0;
  17. }
  18. // 对应的输出:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

注意:block2在MRC环境下的类型为__NSStackBlock__,是存储在栈段的。只有通过copy修饰才会变成__NSMallocBlock__,存储在堆中。在ARC环境下即使不用copy修饰类型也是__NSMallocBlock__,因为编译器会视情况自动进行copy操作

block的copy操作

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

1.block作为函数返回值

如果不进行copy操作myblock内部的block返回值作用域一结束就会被释放

  1. typedef void (^Block)(void);
  2. Block myblock()
  3. {
  4. int age = 10;
  5. return ^{
  6. NSLog(@"---------%d", age);
  7. };
  8. }
  9. int main(int argc, const char * argv[]) {
  10. @autoreleasepool {
  11. Block block = myblock();
  12. block();
  13. NSLog(@"%@", [block class]); // __NSMallocBlock__
  14. }
  15. return 0;
  16. }

2.将block赋值给__strong指针时

  1. typedef void (^Block)(void);
  2. int main(int argc, const char * argv[]) {
  3. @autoreleasepool {
  4. int age = 10;
  5. // 强指针Block block
  6. Block block = ^{
  7. NSLog(@"---------%d", age);
  8. };
  9. NSLog(@"%@", [block class]); // __NSMallocBlock__
  10. }
  11. return 0;
  12. }

3.block作为Cocoa API中方法名含有usingBlock的方法参数时

  1. NSArray *array = @[];
  2. [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  3. }];

4.block作为GCD API的方法参数时

  1. static dispatch_once_t onceToken;
  2. dispatch_once(&onceToken, ^{
  3. });
  4. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  5. });

不同环境下block属性的写法

1.MRCblock属性的建议写法

  1. @property (copy, nonatomic) void (^block)(void);

2.ARCblock属性的建议写法

  1. // 因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);

对象类型的auto变量

当block内部访问了对象类型的auto变量时1.如果block是在栈上,将不会对auto变量产生强引用
  1. @interface Person : NSObject
  2. @property (assign, nonatomic) int age;
  3. @end
  4. @implementation Person
  5. - (void)dealloc
  6. {
  7. [super dealloc];
  8. NSLog(@"Person - dealloc");
  9. }
  10. @end
  11. typedef void (^Block)(void);
  12. int main(int argc, const char * argv[]) {
  13. @autoreleasepool {
  14. Block block;
  15. {
  16. Person *person = [[Person alloc] init];
  17. person.age = 10;
  18. block = ^{
  19. NSLog(@"---------%d", person.age);
  20. };
  21. // MRC环境下对应的内存管理
  22. [person release];
  23. NSLog(@"------%@", [block class]);
  24. }
  25. // 在这里打断点,由于MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
  26. NSLog(@"------");
  27. }
  28. return 0;
  29. }

2.如果block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作

  1. @interface Person : NSObject
  2. @property (assign, nonatomic) int age;
  3. @end
  4. @implementation Person
  5. - (void)dealloc
  6. {
  7. NSLog(@"Person - dealloc");
  8. }
  9. @end
  10. typedef void (^Block)(void);
  11. int main(int argc, const char * argv[]) {
  12. @autoreleasepool {
  13. Block block;
  14. {
  15. Person *person = [[Person alloc] init];
  16. person.age = 10;
  17. // __strong Person *weakPerson = person;
  18. __weak Person *weakPerson = person;
  19. block = ^{
  20. NSLog(@"---------%d", weakPerson);
  21. };
  22. NSLog(@"------%@", [block class]);
  23. }
  24. // 在这里打断点,在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
  25. NSLog(@"------");
  26. }
  27. return 0;
  28. }

将上面代码文件转换成C++文件可以看出,block内部的__main_block_desc_0结构体会调用copy函数copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

  1. struct __main_block_impl_0 {
  2. struct __block_impl impl;
  3. struct __main_block_desc_0* Desc;
  4. Person *__strong person;
  5. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
  6. impl.isa = &_NSConcreteStackBlock;
  7. impl.Flags = flags;
  8. impl.FuncPtr = fp;
  9. Desc = desc;
  10. }
  11. };
  12. static struct __main_block_desc_0 {
  13. size_t reserved;
  14. size_t Block_size;
  15. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  16. void (*dispose)(struct __main_block_impl_0*);
  17. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  18. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

3.如果block从堆上移除,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的auto变量(release)

  1. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

4.block只有引用的是基本数据类型才不会生成copydispose函数

5.如果用static修饰对象类型,那么生成的C++代码如下

  1. Block block;
  2. {
  3. static NSString *string = @"haha";
  4. block = ^{
  5. NSLog(@"---------%@", string);
  6. };
  7. }
  8. // 生成的C++代码
  9. struct __main_block_impl_0 {
  10. struct __block_impl impl;
  11. struct __main_block_desc_0* Desc;
  12. // string变量的类型是NSString **
  13. NSString *__strong *string;
  14. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *__strong *_string, int flags=0) : string(_string) {
  15. impl.isa = &_NSConcreteStackBlock;
  16. impl.Flags = flags;
  17. impl.FuncPtr = fp;
  18. Desc = desc;
  19. }
  20. };

注意:代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 源文件
  2. ```### __block修饰符
  3. #### __block修饰基本数据类型
  4. 看下面代码,怎样可以在`block`内部修改`age`的值

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
@autoreleasepool {

  1. int age = 10;
  2. Block block1 = ^{
  3. // age = 20;
  4. NSLog(@"age is %d", age);
  5. };
  6. block1();
  7. NSLog(@"age的内存地址 - %p", &age);
  8. }
  9. return 0;

}

  1. 1.`static`来修饰`age属性``block`内部引用的是`age`的地址值,可以根据地址去修改`age`的值。但不好的是`age属性`会一直存放在内存中不销毁,造成多余的内存占用,而且会改变`age属性`的性质,不再是一个`auto变量`

static int age = 10;

  1. 2.`__block`来修饰属性,底层会生成`__Block_byref_age_0`类型的结构体对象,里面存储着`age`的真实值

__block int age = 10;

  1. 3.转换成`C++文件`来查看内部结构,会根据`__main_block_impl_0`里生成的`age`对象来修改内部的成员变量`age`而且在外面打印的`age`属性的地址值也是`__Block_byref_age_0`结构体里的成员变量`age`的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值

struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // 保存的自己的地址
int __flags;
int __size;
int age;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

  1. // __Block_byref_age_0 age = {0, &age, 0, sizeof(__Block_byref_age_0), 10};
  2. __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
  3. Block block1 = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
  4. ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
  5. }
  6. return 0;

}

  1. ##### 总结:
  2. - `__block`可以用于解决`block`内部无法修改`auto`变量值的问题
  3. - 编译器会将`__block`变量包装成一个对象
  4. - 其实修改的变量是`__block`生成的对象里面存储的变量的值,而不是外面的`auto变量`,但是内部生成的相同的变量的地址和外面的`auto变量`地址值是一样的,所以修改了内部的变量也会修改了外面的`auto变量`
  5. - `__block`不能修饰全局变量、静态变量(static
  6. ##### __block的内存管理
  7. 1.程序编译时,`block``__block`都是在栈中的,这时并不会对`__block`变量产生强引用
  8. 2.因为`__block`也会包装成`OC对象`,所以`block`底层也会生成`copy函数``dispose函数`
  9. 3.`block``copy`到堆时,会调用`block`内部的`copy函数``copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会对`__block`变量形成强引用(retain

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};

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->age, (void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}

  1. 实际上这时`__block`修饰的变量因为被包装成了`OC对象`,所以也会被拷贝到堆上,如果再有`block`强引用`__block`,由于`__block`变量已经拷贝到堆上了,就不会再拷贝了,下图可以很好的表达出关系
  2. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131050-2137722604.jpg)
  3. 3.`block`从堆中移除时,会调用`block`内部的`dispose函数``dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放引用的`__block`变量(release

static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->age, 8/BLOCK_FIELD_IS_BYREF/);}

  1. 如果有多个`block`同时持有着`__block`变量,那么只有所有的`block`都从堆中移除了,`__block`变量才会被释放
  2. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131097-86614401.jpg)
  3. ##### __block和OC对象在block中的区别
  4. 看下面的代码,在`block`中的本质区别是什么

__block int age = 10;
NSObject *obj = [[NSObject alloc] init];

Block block1 = ^{
age = 20;

NSLog(@"age is %d", age);
NSLog(@"obj is %p", obj);
};

  1. 转成`C++文件`发现,`__block`生成的对象就是强引用,而`NSObject`对象会根据修饰符`__strong`或者`__weak`来区分是否要进行`retain操作`

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong obj;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__strong _obj, __Block_byref_age_0 *_age, int flags=0) : obj(_obj), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->age, (void)src->age, 8/BLOCK_FIELD_IS_BYREF/);_Block_object_assign((void)&dst->obj, (void)src->obj, 3/BLOCK_FIELD_IS_OBJECT/);}

  1. **注意:`__weak`不能修饰基本数据类型,编译器会报`__weak' only applies to Objective-C object or block pointer types; type here is 'int'`警告**
  2. ##### __forwarding指针
  3. - 在栈中,`__block`中的`__forwarding指针`指向自己的内存地址
  4. - 复制到堆中之后,`__forwarding指针`指向堆中的`__block`
  5. - 堆中的`__forwarding`指向堆中的`__block`
  6. - 这样的目的都是为了不论访问的`__block`是在栈上还是在堆上,都可以通过`__forwarding指针`找到存储在堆中的`auto变量`
  7. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130733-2067416101.jpg)
  8. #### __block修饰对象类型
  9. 1.看下面代码,用`__block`修饰的对象类型什么时候被释放

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;

  1. {
  2. Person *person = [[Person alloc] init];
  3. person.age = 10;
  4. __block Person *weakPerson = person;
  5. block = ^{
  6. NSLog(@"---------%d", weakPerson.age);
  7. };
  8. NSLog(@"------%@", [block class]);
  9. }
  10. // 在这里打断点观察person是否会被释放
  11. NSLog(@"------");
  12. }
  13. return 0;

}

  1. 2.转换成`C++文件`可以发现,`__block`底层生成的结构体里面会引用着该对象类型,并且默认是用`__strong`来修饰,而且内部也会对应的生成`copy``dispose`函数

struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (
__Block_byref_id_object_copy)(void
, void);
void (
__Block_byref_id_object_dispose)(void*);
Person *__strong weakPerson;
};

  1. 3.我们看`main函数`里会将`__Block_byref_id_object_copy_131``__Block_byref_id_object_dispose_131`赋值给`__Block_byref_weakPerson_0`这个结构体对象

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Block block;

  1. {
  2. Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
  3. ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
  4. // __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
  5. __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
  6. block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));
  7. NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
  8. }
  9. NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_2);
  10. }
  11. return 0;

}

  1. 4.找到这两个值能发现也是分别会调用`_Block_object_assign``_Block_object_dispose`这两个函数,而且传的对象就是`__Block_byref_weakPerson_0`内部的`weakPerson`这个对象,也就是说这个结构体内部也会对`weakPerson`这个对象进行着`retain``release`的操作

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. 5.我们把第一段代码中的`weakPerson`加上`__weak`修饰符,再运行程序会发现,当作用域结束后,`person`对象也会被释放了

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;

  1. {
  2. Person *person = [[Person alloc] init];
  3. person.age = 10;
  4. __block __weak Person *weakPerson = person;
  5. block = ^{
  6. NSLog(@"---------%d", weakPerson.age);
  7. };
  8. NSLog(@"------%@", [block class]);
  9. }
  10. // 在这里打断点观察person是否会被释放
  11. NSLog(@"------");
  12. }
  13. return 0;

}

  1. 6.我们转换成`C++文件`能发现,`__Block_byref_weakPerson_0`里面的`person`对象修饰符变成了`__weak`

struct __Block_byref_weakPerson_0 {
void __isa;
__Block_byref_weakPerson_0 __forwarding;
int __flags;
int __size;
void (
__Block_byref_id_object_copy)(void
, void);
void (
__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};

  1. ##### 总结:
  2. - `__block`修饰的对象类型也会生成一个新的结构体对象,并且只会被`block`进行强引用,同`__block`修饰基本数据类型是一样的
  3. - `__block`内部也会生成该对象类型的成员变量,而且会根据不同的修饰符`__strong``__weak`来对应着该对象类型是否被强引用
  4. - `__block`内部也会生成`copy``dispose`函数
  5. - `__block`变量被`copy`到堆时,会调用`__block`变量内部的`copy函数``copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会根据所指向对象的修饰符`(__strong、__weak、__unsafe_unretained)`做出相应的操作,形成强引用(retain)或者弱引用
  6. - 如果`__block`变量从堆上移除,会调用`__block`变量内部的`dispose函数``dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放指向的对象(release
  7. **注意:在MRC环境下即使用\_\_block修饰,\_\_block内部只会对auto变量进行弱引用,无论加不加__weakblock还没有释放,\_\_block修饰的变量就已经释放了,这点和在ARC环境下不同**
  8. ### 循环引用
  9. `block`在使用中很容易就会造成循环引用问题,例如下面的代码

typedef void (^Block) (void);

@interface Person : NSObject
@property (copy, nonatomic) Block block;
@property (assign, nonatomic) int age;

  • (void)test;
    @end

@implementation Person

  • (void)test {
    // 内部循环引用
    self.block = ^{
    NSLog(@"age is %d", self.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

  1. // 循环引用
  2. person.block = ^{
  3. NSLog(@"age is %d", person.age);
  4. };
  5. }
  6. NSLog(@"111111111111");
  7. return 0;

}

  1. `person`对象里面的`block`属性强引用着`block`对象,而`block`对象内部也会有一个`person`的成员变量指向这个`Person对象`,这样就会造成循环引用,谁也无法释放
  2. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130088-1235768827.jpg)
  3. #### 解决方法
  4. ##### 在ARC环境下
  5. 让其中一个指针变成弱引用
  6. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024129668-1990190805.jpg)
  7. 1.`__weak`解决,不会产生强引用,当指向的对象销毁时,会自动让指针置为`nil`

@implementation Person

  • (void)test {
    __weak typeof(self) weakSelf = self;

    self.block = ^{
    NSLog(@"age is %d", weakSelf.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

  1. // __weak Person *weakPerson = person;
  2. __weak typeof(person) weakPerson = person;
  3. person.block = ^{
  4. NSLog(@"age is %d", weakPerson.age);
  5. };
  6. }
  7. NSLog(@"111111111111");
  8. return 0;

}

  1. 2.`__unsafe_unretained`解决,不会产生强引用,但是是不安全的,当指向的对象销毁时,指针存储的地址值不变,仍然是指向着那块已经被回收的内存空间,那么再访问这个这个变量就会造成野指针错误

@implementation Person

  • (void)test {
    __unsafe_unretained typeof(self) weakSelf = self;

    self.block = ^{
    NSLog(@"age is %d", weakSelf.age);
    };
    }
    @end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;

  1. __unsafe_unretained Person *weakPerson = person;
  2. person.block = ^{
  3. NSLog(@"age is %d", weakPerson.age);
  4. };
  5. }
  6. NSLog(@"111111111111");
  7. return 0;

}

  1. 3.``__block``解决,用`__block`修饰对象会造成三者相互引用造成循环引用,需要手动调用block

int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;

  1. person.block = ^{
  2. NSLog(@"age is %d", person.age);
  3. person = nil;
  4. };
  5. }
  6. person.block();
  7. NSLog(@"111111111111");
  8. return 0;

}

  1. `block`内部也需要手动将`person`置空,这个`person``__block`内部生成的指向`Person对象`的变量
  2. ![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130614-1682005821.jpg)
  3. ##### 在MRC环境下
  4. 1.`__unsafe_unretained`解决,同`ARC环境下`一样,只是`MRC`不支持`__weak`

Person *person = [[Person alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;

person.block = [^{
NSLog(@"age is %d", weakPerson.age);
} copy];

[person release];

  1. 2.`__block`解决,在`MRC`中,`__block`对象里是不会对`person对象`进行强引用的,所以不会造成循环引用

__block Person *person = [[Person alloc] init];
person.age = 10;

person.block = [^{
NSLog(@"age is %d", person.age);
} copy];

[person release];

  1. ### 面试题
  2. #### 1.看下面代码,分别输入的值是什么

int a = 10;
static int b = 10;

int main(int argc, const char * argv[]) {
@autoreleasepool {

  1. auto int age = 10;
  2. static int height = 10;
  3. void (^block)(void) = ^{
  4. NSLog(@"age is %d, height is %d", age, height);
  5. NSLog(@"a is %d, b is %d", a, b);
  6. };
  7. age = 20;
  8. height = 20;
  9. a = 20;
  10. b = 20;
  11. block();
  12. // 输出结果为:age=10,height=20,a=20,b=20
  13. }
  14. return 0;

}

  1. `age`是自动变量,是值传递
  2. `height`表示的是指针传递,`block`捕获的是该变量的地址
  3. `a、b`都为全局变量,所以`block`根本不用捕获,需要时直接拿取当前最新的值就可以了

int a = 10;
static int b = 10;

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int age = 10;
static int height = 10;

  1. void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
  2. age = 20;
  3. height = 20;
  4. a = 20;
  5. b = 20;
  6. ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  7. }
  8. return 0;

}

  1. #### 2.看下面代码,block内部会不会捕获self

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

  • (instancetype)initWithName:(NSString *)name;
    @end

@implementation Person

  • (void)test
    {
    void (^block)(void) = ^{
    NSLog(@"-------%d", [self name]);
    };
    block();
    }

  • (instancetype)initWithName:(NSString *)name
    {
    if (self = [super init]) {
    self.name = name;
    }
    return self;
    }

@end

  1. 会捕获。因为`self`本质也是一个局部变量,`block`内部会生成一个变量来保存`Person对象`的地址

struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// 函数都会生成隐式参数self和_cmd
static void _I_Person_test(Person * self, SEL _cmd) {
void (block)(void) = ((void ()())&__Person__test_block_impl_0((void )__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void (
)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

  1. #### 3.\_\_block的作用是什么?有什么使用注意点
  2. 可以将修饰的变量包装成一个对象,解决在`block`内部无法修改外部变量的问题。
  3. `__block`内部会进行内存管理,还有在`MRC环境下`是不会对对象进行强引用
  4. #### 4.block的属性修饰词为什么是copy?使用block有哪些使用注意?
  5. `block`一旦没有进行`copy操作`,就不会在堆上。放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理。
  6. 注意循环引用

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-siblock.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号