关于block中调用super造成循环引用的探究

前言

最近刚好学到了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一下 ,发现一切都正常了~

你可能感兴趣的:(关于block中调用super造成循环引用的探究)