美文网首页
总结:解决NSTimer循环引用的五大方法

总结:解决NSTimer循环引用的五大方法

作者: T_Choues | 来源:发表于2021-04-22 10:14 被阅读0次

    NSTimerCADisplayLink不小心处理的话,极易造成循环引用。不管target使用weak还是strong修饰,timer都会对target强引用,而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、将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);
        }
    }
    
    

    5、使用带block的API

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

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

    总结

    综上所述,第4和第5是最佳解决方法。

    相关文章

      网友评论

          本文标题:总结:解决NSTimer循环引用的五大方法

          本文链接:https://www.haomeiwen.com/subject/fosxrltx.html