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