可能是最全的解决NSTimer循环引用的方法

·  阅读 3762

image

前言

使用NSTimerCADisplayLink时不小心处理的话,极易造成循环引用。不管target使用weak还是strong修饰,timer都会对target强引用。

image.png

runloop本身也会对timer强引用,造成runloop引用timer,timer引用target的情况。如果target是控制器的话,控制器就不能释放。

要解决这个问题,就要打断这种引用链条。

1、在viewWillDisappear里处理

viewWillDisappear里面,调用timer的invalidate方法。

不足:push进入其他控制器页面时,本页面也会调用timer的invalidate方法,造成timer失效。

2、在willMoveToParentViewController里处理

willMoveToParentViewController方法里,对timer做invalidate操作。如果控制器外面是容器控制器,进入控制器时和返回上一个控制器时会调用该方法,我们在返回上一个控制器时,调用timer的invalidate方法。

- (void)willMoveToParentViewController:(UIViewController *)parent {
    if (self.viewLoaded) {
        if (_timer.valid) {
            [_timer invalidate];
            _timer = nil;
        }
    }
}
复制代码

不足:只有当控制器外层是容器控制器时,例如UINavigationController,才能使用此方法。

3、将timer的target指向中介者

使用中介者,将timer的target指向这个中介者。 当timer调用中介者的响应方法时,我们通过消息转发机制,让控制器去实际响应这个方法。这样一来,控制器强引用timer, timer强引用中介者, 中介者弱引用控制器,控制器可以正常释放,然后在控制器的dealloc里面调用timer的invalidate方法。

中介者可以继承自NSObject或者NSProxy。继承自NSObject时,若在当前类找不到需要调用的方法,要走完整的消息查找流程和转发流程。而继承自NSProxy时,则直接触发转发流程,省去了去父类查找方法的过程,省去了动态方法解析的过程,效率比使用NSObject高。

ViewController.m:

_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[TTRealProxy proxyWithTarget:self] selector:@selector(timerFire) userInfo:nil repeats:YES];

- (void)dealloc {
    [_timer invalidate];
}
复制代码

TTRealProxy.h:

@interface TTRealProxy : NSProxy

//弱引用
@property (nonatomic, weak) id target;

+ (id)proxyWithTarget:(id)target;

@end
复制代码

TTRealProxy.m:

@implementation TTRealProxy

+ (id)proxyWithTarget:(id)target {
    TTRealProxy *proxy = [TTRealProxy alloc];
    proxy.target = target;
    return proxy;
}

////若不实现此方法, 则直接进入消息转发流程
//- (void)timerFire {
//    NSLog(@"timerFire");
//}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [_target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:_target];
}

@end

复制代码

不足:比较繁琐。

4、使用带block的API

使用带block的API。 不需要使用target,就不需要对控制器进行引用了。

不足:只有NStimer有带block的API,CADisplayLink是没有的。

5、将timer的target指向类对象 👍🏻

将timer的target指向NSTimer类对象。类对象不用考虑引用和释放的问题。由于没有对控制器强引用,控制器可以正常释放,然后在控制器的dealloc方法里面调用timer的invalidate方法。

参考YYKit的实现:

// NSTimer+YYAdd.m

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
    return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}

+ (void)_yy_ExecBlock:(NSTimer *)timer {
    if ([timer userInfo]) {
        void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
        block(timer);
    }
}

复制代码

总结

综上所述,将timer的target指向类对象是最佳的解决方法。

分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改