1. Block 本质
block 本质上也是一个 OC 对象,它内部也有个 isa 指针
block 是封装了函数调用以及函数调用环境的 OC 对象
block 的底层结构如下图所示
源码解析:
struct __GSBlock__load_block_impl_0 {struct __block_impl impl;____①____struct __GSBlock__load_block_desc_0* Desc;____②____int age; // 外部的变量__GSBlock__load_block_impl_0(void *fp, struct __GSBlock__load_block_desc_0 *desc, int _age, int flags=0) : age(_age) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};
========================================
① __block_impl:
struct __block_impl {void *isa; // 内部有isa指针int Flags;int Reserved;void *FuncPtr; //存放代码块中内容的函数的地址};
========================================
② __GSBlock__load_block_desc_0:// 描述Block
static struct __GSBlock__load_block_desc_0 {size_t reserved;size_t Block_size; // 占多少内存}
========================================
__GSBlock__load_block_func_0: // Block代码块内的函数static void __GSBlock__load_block_func_0(struct __GSBlock__load_block_impl_0 *__cself, int a, int b) {int age = __cself->age; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_pg_w290bty14td49582fr3nz9hh0000gn_T_GSBlock_f179b1_mi_0,age);
block的变量捕获(capture) 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制。
auto:自动变量,离开作用域就销毁。
全局变量不会捕获,因为全局在任何地方都可以访问,不需要捕获。
static 类型的变量被捕捉进 block 是 *。
- 例1:static int age = 10;
- 在^{ nslog(@“%@”,age)};
- 在 block 内部是 int *age。
- Person *person = [Person alloc] init];
方法的默认参数:
- -(void)test;-(void)test:(Person *self, SEL _cmd);
- -(void)test:(id *self, SEL _cmd);
- -(void)test:(Person *self, SEL _cmd, NSString *name);
self 为局部变量,所有的参数都是局部变量。
2. Block分类
block 有 3种类型,可以通过调用 class 方法或者 isa 指针查看具体类型,最终都是继承自 NSBlock 类型
NSGlobalBlock ( _NSConcreteGlobalBlock )全局 block 没有访问 auto变量
NSStackBlock ( _NSConcreteStackBlock )栈 block 访问了 auto 变量
NSMallocBlock ( _NSConcreteMallocBlock )堆 block __NSStackBlock__调用了copy
堆:
动态分配内存,需要申请内存,需要管理内存。
- malloc(20) 分配内存,free( ) 释放内存。
- [NSObject alloc]
栈:
存放局部变量,静态变量,特点是自动分配内存,自动销毁。
- 程序数据段:全局变量。
- 程序区域和数据区域由编译器控制。
NSGlobalBlock:没有访问 auto 变量 ,调用 copy 依然是 NSGlobalBlock。
NSStackBlock:访问了 auto 变量 ,调用 copy 是 NSMallocBlock类型。
NSMallocBlock:NSStackBlock 调用 copy 就会成为 NSMallocBlock ,再调用 copy 依然是 NSMallocBlock,引用计数增加1。
每一种 Block 调用 copy 后的结果如下:
ARC环境下,stackBlock 会默认进行copy操作变为 mallocBlock。
栈空间中的 block 是不会去持有外部对象的,如果是堆空间中的 block 是可以持有外部对象的。
3. Block copy
在 ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下情况:
- block 作为函数返回值时。
- 将 block 赋值给 __strong 指针时。
- block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时。
NSArray *array = @[];[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {}];
- block 作为 GCD API 的方法参数时。
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{});dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});
对象类型的 auto 变量
栈空间中的 block 是不会去持有外部对象的,如果是堆空间中的 block 是可以持有外部对象的。
__weak 问题解决
在使用 clang 转换 OC 为 C++代码时,可能会遇到一下问题:
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
- 当 block 内部访问了对象类型的 auto 变量时如果 block 是在栈上,将不会对 auto 变量产生强引用。
- 会调用 block 内部的 copy 函数
- 会调用 block 内部的 dispose 函数。
copy 函数 与 dispose 函数的调用时机
MRC 下 block 属性的建议写法
- @property (copy, nonatomic) void (^block)(void);
ARC 下 block 属性的建议写法
- @property (strong, nonatomic) void (^block)(void);
- @property (copy, nonatomic) void (^block)(void);
4. __Block修饰符
__block 可以用于解决 block 内部无法修改 auto 变量值的问题。
__block 不能修饰全局变量、静态变量(static)。
编译器会将 __block 变量包装成一个对象。
__forwarding:指向自身的指针,存放的是__Block_byref_age_0 自己的内存地址。
5. __Block 内存管理
__block 修饰的变量是强引用。
-
强指针:默认情况下,所有的指针都是强指针。我们也可以用__strong修饰。
-
弱指针:用__weak修饰的指针,就是弱指针。
-
当 block 在栈上时,并不会对 __block 变量产生强引用。
-
当 block 被 copy 到堆时,会调用 block 内部的 copy 函数,copy 函数内部会调 _Block_object_assign 函数,_Block_object_assign 函数会对 __block 变量形成强引用(retain)。
-
当 block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部会调用_Block_object_dispose 函数,_Block_object_dispose 函数会自动释放引用的 __block 变量(release)
6. Block 循环引用
ARC:用__weak、__unsafe_unretained解决。
MRC:用__Block、__unsafe_unretained解决。
- 用 __weak、__unsafe_unretained 解决__weak typeof(self)weakSelf = self; //__typeof(self)在Xcode 6已不再提示,提倡使用typeof(self)
- typeof(*) 返回 * 的类型。
- __weak:不会产生强引用。指向的对象销毁时,会自动让指针置为nil。
- __unsafe_unretained:不会产生强引用,不安全。指向的对象销毁时,指针存储的地址值不变。
- 此方案的弊端是,必须要执行 block( ),block 体内 需要做吧 weakself 置为 nil 的打破循环的操作。
7. 对象类型的 auto 变量、 __block 变量
- 当 block 在栈上时,对它们都不会产生强引用。
- 当 block 拷贝到堆上时,会通过 copy 来处理它们。__block变量(假设变量名叫做a)
- _Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
- 对象类型的auto变量(假设变量名叫做p)
- _Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
- __block变量(假设变量名叫做a)
8. 被 __block 修饰的对象类型
- 当 __block 变量在栈上时,不会对指向的对象产生强引用。
- 当 __block 变量被 copy 到堆时会调用 __block 内部的 copy 函数。
- copy 函数内部会调用 _Block_object_assign 函数
- _Block_object_assign 函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。(注意:这里仅限于 ARC 的 retain,MRC 时不会 retain)
- 会调用 block 内部的 dispose 函数
- 点赞
- 收藏
- 分享
- 文章举报
GS-NICE发布了177 篇原创文章 · 获赞 0 · 访问量 2707私信关注