解决NSTimer或CADisplayLink计时器造成的循环引用问题。

众所周知,我们在使用NSTimer或者CADisplayLink的时候,经常会导致引用它们的类不能正常释放,那是因为引用它们的类与它们之间产生了循环引用。看以下代码:

self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];

 self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(runDipslay)];

self引用了timer和displayLink,而它们又强引用了self,这样就形成了强应用。

那么如何解除这种强引用呢?

关于timer我们可以使用block的回调形式,然后在block内部引用self的时候,添加__weak即可:

 __weak typeof(self) weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) {
            [timer invalidate];
            return;
        }
        [strongSelf runTimer];
    }];

但是CADisplayLink并没有提供block的回调方式,因此我们还有另一种解决方案,这个方案是NSTimer和CADisplayLink通用的:(原理就是利用消息转发机制)

首先我们创建一个NSProxy的子类,然后添加一个weak类型的id指针,用于存储消息转发的对象,然后实现NSProxy的消息转发机制,代码如下:

.h
@interface MyTargetProxy : NSProxy

+ (instancetype)weakProxyTarget:(id)target;

@end


.m
@interface MyTargetProxy ()

@property (nonatomic, weak) id target;

@end

@implementation MyTargetProxy

+ (instancetype)weakProxyTarget:(id)target{
    MyTargetProxy *proxy = [MyTargetProxy alloc];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)selector{
    return _target; // 将消息转发给_target对象
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
  // 因为_target示弱引用,所以可能为nil的情况,当target=nil,就会走这里,这里我们创建一个没有参数,有返回值的方法签名
return [NSObject instanceMethodSignatureForSelector:@selector(init)]; } - (void)forwardInvocation:(NSInvocation *)invocation{ void *nullPointer = NULL; [invocation setReturnValue:&nullPointer]; // 这里我们只需要返回nil即可 } @end

然后在使用NSTimer的时候如下:

MyTargetProxy *target = [MyTargetProxy weakProxyTarget:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:2
                                                  target:target
                                                selector:@selector(runTimer)
                                                userInfo:nil
                                                 repeats:YES];

最后记得在self的delloc中,调用[self.timer invalidate],这样我们就完美的解决了NSTimer中的循环引用问题。

关于为啥这个MyTargetProxy要选择继承自NSProxy,我们可以点击这里了解。

另外我们可以看到NSProxy中的forwardingTargetForSelector方法是被注释的,关于这里的解释,我们可以点击这里了解。

 

posted @ 2020-03-23 21:42  zbblogs  阅读(445)  评论(0编辑  收藏  举报