美文网首页
定时器强引用问题

定时器强引用问题

作者: iOS_Yang | 来源:发表于2019-01-31 16:57 被阅读0次

    通常使用定时器有NSTimer 和 CADisplayLink,但是在使用过程中总会强引用当前对象,如下:

    // scheduled开头创建的定时器默认已经添加到RunLoop中
    self.timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
    
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(testTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(testLink)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    避免设置target强引用解决方案
    方案一
    由于NSTimer可以通过block的方式直接设置定时任务可以避免设置target引起的强引用。

    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            // 定时任务
    }];
    

    方案二

    创建一个代理TimerProxy,将代理作为定时器target。

    image
    TimerProxy.h
    
    #import <Foundation/Foundation.h>
    
    @interface HTTimerProxy : NSObject
    + (instancetype)timerProxyWithTaget:(id)target;
    @property (nonatomic, weak) id target;
    @end
    
    TimerProxy.m
    
    #import "HTTimerProxy.h"
    
    @implementation HTTimerProxy
    
    + (instancetype)timerProxyWithTaget:(id)target
    {
        HTTimerProxy *proxy = [[self alloc] init];
        proxy.target = target;
        return proxy;
    }
    
    // 利用runtime的消息转发
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        return self.target;
    }
    @end
    

    NSTimer与CADisplayLink将HTTimerProx作为代理

    // scheduled开头创建的定时器默认已经添加到RunLoop中
    self.timer =[NSTimer scheduledTimerWithTimeInterval:1.0 target:[HTTimerProxy timerProxyWithTaget:self] selector:@selector(testTimer) userInfo:nil repeats:YES];
    
    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:[HTTimerProxy timerProxyWithTaget:self] selector:@selector(testLink)];
    [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    

    方案三
    创建一个集成NSObject的分类TimerWeakTarget,创建类方法---开启定时器的方法

    #import <Foundation/Foundation.h>
    
    @interface TimerWeakTarget : NSObject
    
    @property (nonatomic, assign) SEL selector;
    @property (nonatomic, weak) NSTimer *timer;
    @property (nonatomic, weak) id target;
    
    
    /** 
     1.重写开启定时器方法,在内部对target进行替换,换成本类(TimerWeakTarget)的对象即可
     2.不会造成循环引用了,原控制器OneViewController属性有timer对timer强应用,timer内部对self强引用,但是self在此方法内部被替换成了本类的对象(TimerWeakTarget *),而本类的对象不会对OneViewController强引用,则不会造成循环引用,也就不会造成内存泄露
     */
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)aTarget
                                        selector:(SEL)aSelector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats;
    
    @end
    
    • TimerWeakTarget.m文件中
    • 在下面我们封装的类的方法中,我们将开启定时器的方法 [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];中的target换掉了,换成了 本类的对象,timer.在OneViewController中开启定时器的时候直接调用这个类方法,就不会造成循环引用.看图


      image.png
    #import "TimerWeakTarget.h"
    
    @implementation TimerWeakTarget
    
    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)aTarget
                                        selector:(SEL)aSelector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats{
    
        TimerWeakTarget * timer = [TimerWeakTarget new];
        timer.target = aTarget;
        timer.selector = aSelector;
        //-------------------------------------------------------------此处的target已经被换掉了不是原来的VIewController而是TimerWeakTarget类的对象timer
        timer.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timer selector:@selector(fire:) userInfo:userInfo repeats:repeats];
        return timer.timer;
    }
    
    -(void)fire:(NSTimer *)timer{
    
        if (self.target) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        } else {
    
            [self.timer invalidate];
        }
    }
    
    @end
    
    
    • 控制器dismiss以后可以正常被销毁.问题解决.

    参考文章
    https://www.cnblogs.com/CoderHong/p/9389753.html

    https://www.cnblogs.com/adampei-bobo/p/5460988.html

    相关文章

      网友评论

          本文标题:定时器强引用问题

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