NSTimer循环引用问题

在实际的开发中,在需要定时器时,这样的代码很常见。
    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 

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 

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 

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循环引用问题)