主要讲解Block 内部使用strongSelf的理由和用法
iOS 题目详解 部分一
iOS 题目详解 部分二
iOS 题目详解 部分三
iOS 题目简述 部分一
Block 内部 self 的正确用法
问题背景:
当前Controller
有一个 Block
属性
@interface ViewController2 ()
@property (nonatomic, strong) NSMutableArray *nameArr;
@property (nonatomic, copy) Block block;
@end
而在 self.block
中要访问当前 Controller
也就是self
;
对Block
没有强硬用的问题背景, 分析思路类似, 不再探讨;
我们日常开发中最常用的就是Block
内部使用weakSelf
可以防止循环引用, 但是这样会有个问题, 如果Block
内部有延时任务执行时这样就不满足需求了, 因为执行延时任务时self
已经被释放;
外部使用weak
后内部使用strongSelf
可以解决这个问题;
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@ ", strongSelf);
});
};
self.block();
但是为什么这样写可以保证不产生循环引用呢? 就这个问题通过clang
看下以下几种情况其底层代码研究下;
- 1.1 首先直接在
Block
内部使用self
会造成循环引用, 这点毋庸置疑;
self.block = ^{
NSLog(@"%@", self);
};
通过clang
后的C++
代码如下:
struct __ViewController2__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController2__viewDidLoad_block_desc_1* Desc;
#Block 内部对 self 强硬用
ViewController2 *const __strong self;
__ViewController2__viewDidLoad_block_impl_1(void *fp, struct __ViewController2__viewDidLoad_block_desc_1 *desc, ViewController2 *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
由于Block
是当前 Controller
的属性, 所以self
对Block
强引用, 而Block
内部又对self
强引用;
- 1.2 在
Block
内部中使用weakSelf
有效的解决循环引用问题;
- (void)viewDidLoad {
[super viewDidLoad];
#weak 修饰 self 不会造成循环引用
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf delayTask];
});
};
self.block();
}
#延时任务
- (void)delayTask {
NSLog(@"%s", __func__);
}
#副本 Block
struct __ViewController2__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController2__viewDidLoad_block_desc_1* Desc;
#Block 对 Controller 弱引用
ViewController2 *const __weak weakSelf;
__ViewController2__viewDidLoad_block_impl_1(void *fp, struct __ViewController2__viewDidLoad_block_desc_1 *desc, ViewController2 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
#原始 Block
struct __ViewController2__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController2__viewDidLoad_block_desc_0* Desc;
ViewController2 *const __weak weakSelf;
__ViewController2__viewDidLoad_block_impl_0(void *fp, struct __ViewController2__viewDidLoad_block_desc_0 *desc, ViewController2 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这种方式我们都知道不会造成循环引用,但是造成的问题随之而来, 如果我们在Block
内部执行了延时的任务(目的是为了执行任务时Controller
已经被销毁); 则会发现, 这个延时任务并不会被执行, 因为执行[weakSelf delayTask]
这句代码时controller
已经被销毁, 给一个 nil
发送消息, 是不会响应的;
- 1.3 正确用法: 外部使用
weak
修饰, 内部使用strongSelf
;
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf delayTask];
});
};
self.block();
}
#延时任务
- (void)delayTask {
NSLog(@"%s", __func__);
}
退出当前Controller
后延时任务可以正常执行, 而且Controller
可以正常释放;
#延时任务被执行
2020-09-08 10:27:32.253938+0800 Test[12274:2082073] -[ViewController2 delayTask]
#延时任务被执行后, Controller正常销毁, 说明没有循环引用
2020-09-08 10:27:32.254415+0800 Test[12274:2082073] -[ViewController2 dealloc]
但是, 为什么这样写就可以保证没有循环引用呢?首先请了解下Block
底层各个函数的含义
首先看下ViewDidLoad
通过clang
后转化为如下, 因为ARC
下我们都知道系统会帮我们把Block
从栈区拷贝到堆区, 我们实际操作的是堆区的那一份Block
副本;
static void _I_ViewController2_viewDidLoad(ViewController2 * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController2"))}, sel_registerName("viewDidLoad"));
__attribute__((objc_ownership(weak))) typeof(self) weakSelf = self;
#Block 的实现
((void (*)(id, SEL, Block))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__ViewController2__viewDidLoad_block_impl_1((void *)__ViewController2__viewDidLoad_block_func_1, &__ViewController2__viewDidLoad_block_desc_1_DATA, weakSelf, 570425344)));
#调用 Block
((Block (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}
可以确认这个self.block
的底层实现是__ViewController2__viewDidLoad_block_impl_1
, 在Block
内部调用的函数是__ViewController2__viewDidLoad_block_func_1
;
首先看下self.block
的底层结构实现
struct __ViewController2__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController2__viewDidLoad_block_desc_1* Desc;
#对 self 弱引用
ViewController2 *const __weak weakSelf;
__ViewController2__viewDidLoad_block_impl_1(void *fp, struct __ViewController2__viewDidLoad_block_desc_1 *desc, ViewController2 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其实到这一步我们已经可以断定知道为什么不会造成循环引用了, 因为Controller
对self.block
是强引用, 而self.block
的底层实现如上, 对Controller
是weak
修饰的弱引用;
不过我们还是继续往下看探究下具体的调用流程, 内部调用函数__ViewController2__viewDidLoad_block_func_1
的实现如下;
static void __ViewController2__viewDidLoad_block_func_1(struct __ViewController2__viewDidLoad_block_impl_1 *__cself) {
ViewController2 *const __weak weakSelf = __cself->weakSelf; // bound by copy
#调用原始 Block, 注意副本 Block 只是调用原始 Block的实现, 并不对其强引用或者持有
__attribute__((objc_ownership(strong))) typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time((0ull), (int64_t)(5 * 1000000000ull)), dispatch_get_main_queue(), ((void (*)())&__ViewController2__viewDidLoad_block_impl_0((void *)__ViewController2__viewDidLoad_block_func_0, &__ViewController2__viewDidLoad_block_desc_0_DATA, strongSelf, 570425344)));
}
原始Block
的实现和调用函数封装逻辑如下;
#原始 Block的底层结构
struct __ViewController2__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController2__viewDidLoad_block_desc_0* Desc;
#对 self 强硬用, 但是没有关系, 因为 self 不对原始 block 强引用;
ViewController2 *const __strong strongSelf;
__ViewController2__viewDidLoad_block_impl_0(void *fp, struct __ViewController2__viewDidLoad_block_desc_0 *desc, ViewController2 *const __strong _strongSelf, int flags=0) : strongSelf(_strongSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
#实际执行延时任务
static void __ViewController2__viewDidLoad_block_func_0(struct __ViewController2__viewDidLoad_block_impl_0 *__cself) {
ViewController2 *const __strong strongSelf = __cself->strongSelf; // bound by copy
#调用延时任务
((void (*)(id, SEL))(void *)objc_msgSend)((id)strongSelf, sel_registerName("delayTask"));
}
下面我们来总结下这个流程; 首先是在 ARC
环境下我们知道系统会对栈区
的原始Block
执行拷贝
操作到堆区
, 我们实际操作的是堆区那份副本;
-
self
强硬用拷贝后的副本Block
, 副本Block
中对self
是弱引用; - 副本
Block
中调用原始Block
; - 原始
Block
对self
强引用;
至此我们可以确定, 因为原始Block
对self
有一个强引用, 肯定会导致self
的引用计数+1 , 关于这点引用计数+1的验证, 由于验证篇幅较长不再贴出, 大家可以自己验证, 或者移步看下这篇文章
下面探讨下释放的流程: ARC
环境下为什么可以正常释放(, MRC
下类似):
- 我们假设当
controller
即将销毁时, 也就是退出当前界面后, 因为原始Block
对其有个强引用, 所以它不能销毁, 需要等待延时任务执行; - 执行到延时任务时,也就是执行到副本
Block
时, 副本Blcok
调用原始Block
的funptr
执行具体Block
内部的实现逻辑; - 我们知道原始
Block
处于栈区, 栈区的内存管理是系统进行的, 所以延时任务执行完毕, 原始Block
自动销毁, 同时对强硬用的self
释放(引用计数-1); - 当
self
的引用计数为0时, 就可以被销毁了, 因为self
对副本Block
有强引用, 所以self
释放的同时会对堆区
的副本Block
销毁; - 至此, 不论是
Controller(self)
, 原始Block
, 副本Block
都可以正常的释放; 而且可以正常的在Block
内部执行延时任务;
补充部分
1. 便捷宏定义使用waekfSelf 和 stongSelf;
每次使用 Block
都写一遍weak
和strong
的定义太麻烦, 将其定义成宏, 方便快捷;
/**
弱引用/强引用 宏定义
示例:
@weakify(self)
[self block^{
@strongify(self)
if (!self) return;
...
}];
*/
#ifndef weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif
#ifndef strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif
使用示例:
- (void)test {
@weakify(self)
self.block = ^{
@strongify(self);
if (!self) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self delayTask];
});
};
self.block();
NSLog(@"Class: %@", [self.block class]);
}
2. 验证下: 关于ARC
下系统自动帮我们把Block
从栈区
拷贝到了堆区
;
- 在
MRC
环境下, 如下代码:
- (void)viewDidLoad {
[super viewDidLoad];
Block block = ^{
NSLog(@"%@", self);
};
block();
NSLog(@"Class: %@", [block class]);
}
打印结果如下
#这是一个栈区 Block
2020-09-08 12:21:12.140994+0800 Test[12396:2111280] Class: __NSStackBlock__
- 在
ARC
环境下, 同样的代码:
- (void)viewDidLoad {
[super viewDidLoad];
Block block = ^{
NSLog(@"%@", self);
};
block();
NSLog(@"Class: %@", [block class]);
}
打印结果如下
#这是一个堆区 Block
2020-09-08 12:22:07.533464+0800 Test[12399:2111818] Class: __NSMallocBlock__
至此可以验证, 在ARC
环境下系统帮我们默认执行了拷贝操作把Block
从栈区
拷贝到了堆区
;
备注: 补上Block
的拷贝流程
参考文章
iOS Block 部分一
iOS Block内部使用 strongSelf引用计数
基本的Clang语句
iOS Block 的拷贝底层实现