美文网首页面试题
NSTimer循环引用问题

NSTimer循环引用问题

作者: 木子雨廷t | 来源:发表于2020-10-29 10:31 被阅读0次
    在实际的开发中,在需要定时器时,这样的代码很常见。
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethond) userInfo:nil repeats:YES];
    
    那么这样代码如果处理不当,很容易引起循环引用问题。
    循环引用产生原因:
    NSTimer 强引用 self(控制器本身)(target:self),控制器本身没有强引用NSTimer。
    NSTimer 被runLoop强引用,runLoop一直不释放,所以NSTimer一直也不释放,所以self(控制器)也不释放
    runLoop -> NSTimer -> self
    解决方法 1(不推荐):
    在控制器 dealloc方法 执行之前,将NSTimer 调用[_timer invalidate];,并将_timer = nil。
    这样可以解决掉循环引用的问题,但是在回到这个界面时,NSTimer 已经不在了,还得重新创建。
    解决方法 2:
     __weakSelf
        self.countNum = 10;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            [weakSelf showNumber];
        }];
    
    使用系统提供的带 block 的方法,但是需要注意在block内部使用weakSelf,防止block的循环引用,在dealloc方法中调用 [_timer invalidate]方法。
    解决方法 3:
    自己给NSTimer 创建一个category ,代码如下:

    .h 文件

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface NSTimer (BlcokTimer)
    
    + (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats;
    
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    .m文件

    #import "NSTimer+BlcokTimer.h"
    
    @implementation NSTimer (BlcokTimer)
    
    + (NSTimer *)bl_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats {
        
        return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats];
    }
    
    + (void)bl_blockSelector:(NSTimer *)timer {
        
        void(^block)(void) = timer.userInfo;
        if (block) {
            block();
        }
    }
    
    @end
    

    调用

    self.timer02 = [NSTimer bl_scheduledTimerWithTimeInterval:1 block:^{
            [weakSelf showNumber02];
        } repeats:YES];
    
    同样需要注意在block内部使用weakSelf,防止block的循环引用,在dealloc方法中调用 [_.timer02 invalidate]方法。
    [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(bl_blockSelector:) userInfo:[block copy] repeats:repeats]; 
    
    写到了类别中那么self 就变成了NSTimer本身,不是VC了。从而避免了NSTimer对VC的强引用。
    解决方法 4:
    定义中间件,代码如下:

    .h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LTWeakObject : NSObject
    
    + (instancetype)proxyWithWeakObject:(id)obj;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    

    .m

    #import "LTWeakObject.h"
    
    @interface LTWeakObject ()
    
    @property (weak, nonatomic) id weakObject;
    
    @end
    
    @implementation LTWeakObject
    
    - (instancetype)initWithWeakObject:(id)obj {
        _weakObject = obj;
        return self;
    }
    
    + (instancetype)proxyWithWeakObject:(id)obj {
        return [[LTWeakObject alloc] initWithWeakObject:obj];
    }
    
    /**
     * 消息转发,让_weakObject响应事件
     */
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        return _weakObject;
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation {
        void *null = NULL;
        [invocation setReturnValue:&null];
    }
    
    - (BOOL)respondsToSelector:(SEL)aSelector {
        return [_weakObject respondsToSelector:aSelector];
    }
    
    @end
    
    用法如下:
    // target要设置成weakObj,实际响应事件的是self
        LTWeakObject *weakObj = [LTWeakObject proxyWithWeakObject:self];
        self.timer03 = [NSTimer scheduledTimerWithTimeInterval:1 target:weakObj selector:@selector(showNumber03) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:self.timer03 forMode:NSRunLoopCommonModes];
    
    实现原理图:
    企业微信截图_5ef80207-9181-4e70-b19d-42660fca0028.png
    解决方法5:
    继承解决,代码如下:

    .h

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface LTTimer : NSObject
    
    @property (copy) NSDate *fireDate;
    @property (readonly) NSTimeInterval timeInterval;
    
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep runloopMode:(NSRunLoopMode)mode;
    
    + (instancetype)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id __nullable)userInfo repeats:(BOOL)yesOrNo runloopMode:(NSRunLoopMode)mode;
    
    + (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo runloopMode:(NSRunLoopMode)mode;
    
    - (void)fire;
    - (void)invalidate;
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    .m

    #import "LTTimer.h"
    #import "LTWeakObject.h"
    
    @interface LTTimer ()
    
    @property (strong, nonatomic) NSTimer *timer;
    
    
    @end
    
    @implementation LTTimer
    
    - (void)dealloc {
        [self.timer invalidate];
         self.timer = nil;
    }
    
    - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep runloopMode:(NSRunLoopMode)mode {
        if (self = [super init]) {
            LTWeakObject *weakProxy = [LTWeakObject proxyWithWeakObject:t];
            
            _timer = [[NSTimer alloc] initWithFireDate:date interval:ti target:weakProxy selector:s userInfo:ui repeats:rep];
            [[NSRunLoop currentRunLoop] addTimer:_timer forMode:mode];
        }
        
        return self;
    }
    
    + (instancetype)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id __nullable)userInfo repeats:(BOOL)yesOrNo runloopMode:(NSRunLoopMode)mode {
        LTTimer *timer = [[LTTimer alloc] initWithFireDate:[NSDate distantFuture] interval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo runloopMode:mode];
        return timer;
    }
    + (instancetype)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo runloopMode:(NSRunLoopMode)mode {
        LTTimer *timer = [[LTTimer alloc] initWithFireDate:[NSDate distantPast] interval:ti target:aTarget selector:aSelector userInfo:userInfo repeats:yesOrNo runloopMode:mode];
        return timer;
        
    }
    
    - (void)fire {
        [self.timer setFireDate:[NSDate distantPast]];
    }
    - (NSDate *)fireDate {
        return self.timer.fireDate;
    }
    - (void)setFireDate:(NSDate *)date {
        [self.timer setFireDate:date];
    }
    - (NSTimeInterval)timeInterval {
        return self.timer.timeInterval;
    }
    - (void)invalidate {
        [self.timer invalidate];
         self.timer = nil;
    }
    @end
    
    使用方法:
    self.timer04 = [LTTimer timerWithTimeInterval:1 target:self selector:@selector(showNumber04) userInfo:nil repeats:YES runloopMode:NSRunLoopCommonModes];
        [self.view addSubview:self.showLable04];
    
    注意:在dealloc方法中调用 [_timer invalidate]方法。

    相关文章

      网友评论

        本文标题:NSTimer循环引用问题

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