前言
最近刚好学到了super的实现原理,然后心血来潮去试了一下。在block中通过super调用父类的方法确实可能会造成循环引用。
实践
@interface ViewController ()
@property (nonatomic, copy) void(^block)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
[super class];
};
}
@end
这时候我们在dealloc里打上断点,当页面离开的时候。dealloc方法并没有执行。所以这里确实造成了循环引用
探究
当使用[self class]时,会调用objc_msgSend函数,第一个参数receiver就是self,而第二个参数,要先找到self所在的这个class的方法列表,如果有,则返回对应的selector并执行,如果没有,则会一层层向上寻找,直到找到为止,如果最后都没能找到,ok,那我们进入消息转发流程。
当使用[super class]时,会调用objc_msgSendSuper函数,此时会先构造一个objc_super的结构体,然后第一个成员变量receiver仍然是self,而第二个成员变量super_class即是所在类的父类。构造完之后,把结构体传入objc_msgSendSuper函数中,然后会从super_class这个类对应的方法列表开始找selector,如果有,则返回对应的selector并执行,如果没有,则会一层层向上寻找,直到找到为止,如果最后都没能找到,会进入消息转发流程。
如此一探究,super调用的流程以及与self去调用的区别就真相大白了。现在再回头来看示例中的block中调用super会导致循环引用的原因以及block如何安全使用super调用问题的答案便已浮出水面了。
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
注意:这里第一个参数 是一个objc_super的结构体 这个结构体的结构为:
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
结构体第一个成员receiver 就代表方法的接受者 第二个成员代表方法接受者的父类
所以
self.block = ^{
[super class];
};
将super用源码展开后:
self.obj = ^{
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(NSClassFromString(@"ViewController")),
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(class));
};
可以很明显的看到问题,block强引用了self,而self也强持有了这个block。
解决方法
正确的调用姿势跟平常我们切断block的循环引用的姿势一模一样:
__weak __typeof(self) weakSelf = self;
self.block = ^{
struct objc_super superInfo = {
.receiver = weakSelf,
.super_class = class_getSuperclass(NSClassFromString(@"ViewController")),
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(class));
};
改完咱们再重新Run一下 ,发现一切都正常了~