美文网首页
关于NSTimer的循环引用问题

关于NSTimer的循环引用问题

作者: Z_Lukas | 来源:发表于2020-04-20 18:48 被阅读0次

    关于NSTimer的介绍

    1、最近在总结知识、好久没记录了,今天抽空记录下NSTimer的使用,首先我们先看下App官方API文档中解释的NSTimer

    ### Summary
    A timer that fires after a certain time interval has elapsed, sending a specified message to a target object.
    ### Discussion
    第一段:
    Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop. 
    
    

    首先看这段的解释,NSTimer的工作依赖NSRunLoop,并且RunLoop保持强持有当前的Timer, 这个时候当你把Timer添加到当前的RunLoop后,你当前的Timer就被Run Loop强持有了

    To use a timer effectively, you should be aware of how run loops operate. See [Threading Programming Guide](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.html#//apple_ref/doc/uid/10000057i) for more information.
    
    A timer is not a real-time mechanism. If a timer’s firing time occurs during a long run loop callout or while the run loop is in a mode that isn't monitoring the timer, the timer doesn't fire until the next time the run loop checks the timer. Therefore, the actual time at which a timer fires can be significantly later. See also [Timer Tolerance](applewebdata://43EE1961-4719-4FAB-9500-13E376222651#1667624).
    
    [NSTimer](apple-reference-documentation://hcaODPrPmP) is toll-free bridged with its Core Foundation counterpart, [CFRunLoopTimerRef](apple-reference-documentation://hcPs68Qze5).
    

    这段讲述了Timer并不是十分精确的,他需要依赖runloop的处理事件循环周期,所以有一个容忍的公差度

    NSTimer是如何造成的当前VC无法释放的

    1587377415988.jpg

    如上图所示,当你使用下列方式Target-Action初始话的时候,造成的当前self无法释放,原因当前的NSRunLoop强持有的Timer,而timer又被self强持有,所以Timer无法释放,self也无法释放,

    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(selector) userInfo:id repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
    

    如何解决上边这种方法定义的Timer呢?就是我需要调用的【self.timer invalidate】方法,下面我们看下官方文档给出的解释


    1587377506737.jpg

    特别是图中的标出来的这句,意思是当你调用invalidate的时候,会打破runLoop强引用Timer,也同时打破Taget对Timer的强持有,这个时候 在看(1587377415988.jpg)此图剩余self对timer单向强持有了,所谓的循环引用也就被打破了,所以打破的重重之重是需要调用invalidate方法。

    总结:

    当我们使用初始化方式是Target-Action这种方式的初始化确实是存在循环引用的也就是下列的方法

    
    需要代码添加到当前的RunLoop中
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
    系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
    需要代码添加到当前的RunLoop中
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    系统自己给你添加到当前的RunLoop中并且是Defalut Mode模式 参考API的注释
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    
    第二种初始化 使用了带有Block的事件回调函数进行的处理,初始化函数,可能苹果意思到定时器存在的循环引用的缺陷,在iOS 10系统以后使用了这种Block回调的 就不存在循环引用了,此时也可以再dealloc函数中调用 invalidate 函数就不会存Target-Action 这种形式初始化的定时器的无法调用的问题了
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    
    /// Initializes a new NSTimer object using the block as the main body of execution for the timer. This timer needs to be scheduled on a run loop (via -[NSRunLoop addTimer:]) before it will fire.
    /// - parameter:  fireDate   The time at which the timer should first fire.
    /// - parameter:  interval  The number of seconds between firings of the timer. If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead
    /// - parameter:  repeats  If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
    /// - parameter:  block  The execution body of the timer; the timer itself is passed as the parameter to this block when executed to aid in avoiding cyclical references
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
    

    介绍完循环引用的问题了,我们看下使用Taregt-Action 这种形式,我们一般是需要选择合适的时机去调用invalidate解除下循环引用让定时器无效,但是这个时机的选择比较不确定,需要根据具体的业务需求如pop出页面的时候,dismiss调页面时候。主动调用invalidate函数
    所以我们不想管他什么时机调用,我们不想手动调用了,我们只关注当前VC释放的时候定时器就无效掉,于是我们需要借助一个中间类去帮我们自动实现这种需求

    类方法:

    创建一个继承NSObject的子类TempTarget,并创建开启计时器的方法。

    TempTarget .h文件

    @property (nonatomic, assign) SEL selector;
    @property (nonatomic, weak) NSTimer *tempTimer;
    @property (nonatomic, weak) id tempTarget;
    
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats;
    

    TempTarget .m文件

    @implementation TempTarget
    
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval target:(id)tempTarget selector:(SEL)selector userInfo:(id)userInfo repeats:(BOOL)repeats {
        TempTarget *target = [[TempTarget alloc] init];
        target.tempTarget = tempTarget;
        target.selector = selector;
        target.tempTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:target selector:@selector(timerSelector:) userInfo:userInfo repeats:repeats];
        return target.tempTimer;
    }
    
    - (void)timerSelector:(NSTimer *)tempTimer {
        if (self.tempTarget && [self.tempTarget respondsToSelector:self.selector]) {
            [self.tempTarget performSelector:self.selector withObject:tempTimer.userInfo];
        }else {
            [self.tempTimer invalidate];
        }
    }
    
    @end
    

    VC调用

    self.timer = [TempTarget scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerStart:) userInfo:nil repeats:YES];
    
    注意

    VC强引用timer,因为timer的target是TempTarget实例,所以timer强引用TempTarget实例,而TempTarget实例弱引用VC,解除循环引用。看TempTarget.h 文件两个所引用属性,其实这种方法本质还是存在循环医用的问题的,根本解决循环引用还是主动调用invalidate方法

    1587379684885.jpg

    相关文章

      网友评论

          本文标题:关于NSTimer的循环引用问题

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