666 字
3 分钟
Timer优化
前言
NSTimer 是 iOS 开发中老生常谈的知识点了,今天再来唠叨一番。
一、 NSTimer 常规使用
通过下面简单的 Demo 看看现象。
#import "NSTimerViewController.h"
@interface NSTimerViewController ()@property (nonatomic, strong) NSTimer *timer;@property (nonatomic, assign) NSInteger count;@end
@implementation NSTimerViewController
- (void)viewDidLoad { [super viewDidLoad];
[self selfTargetTimer];}
#pragma mark - 不同 NSTimer 使用方式/// 常规使用 self 作为 Target 的 NSTimer- (void)selfTargetTimer { self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAction) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];}
#pragma mark - Action- (void)countAction { self.count += 1; NSLog(@"%@ - %ld",self, self.count);}
- (void)dealloc { [self.timer invalidate]; self.timer = nil; NSLog(@"NSTimerViewController dealloc");}
@end我们反复 Push 到这个 ViewController ,出现了典型的内存泄露。

通过内存图我们能够发现,根本原因是 RunLoop -> Timer <-> Self 这样的一个持有关系。

而在该API的文档中也是有说明的。

为了解决内存泄露,我们需要打破这个循环引用。
1.1 weakSelf
我们用 weakself 替代 Self 是否有用呢?
__weak typeof(self)weakSelf = self;self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(countAction) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
还是一样,这里是 RunLoop -> Timer <-> weakSelf -> Self 依旧没有打破。
1.2 block
还有这样一个 API
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;我们试试
__weak typeof(self)weakSelf = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { [weakSelf countAction]; }];
这个 API 就可以很好的避免循环引用了。这里的关系是 RunLoop -> Timer -> block 。这里只需要注意不要让 block 产生内存泄露就ok了。
二、 自定义 Timer
2.1 加一层间接
通过封装,解决 Timer 和 Self 的强引用关系: RunLoop -> Timer -> ?? ~~ Self
#import <objc/message.h>
@interface RYTimer ()
@property (nonatomic, weak) id target;@property (nonatomic, assign) SEL aSelector;@property (nonatomic, strong) NSTimer *timer;
@end
@implementation RYTimer
- (instancetype)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo { if (self == [super init]) { self.target = aTarget; self.aSelector = aSelector;
if ([self.target respondsToSelector:self.aSelector]) { Method method = class_getInstanceMethod([self.target class], aSelector); const char *type = method_getTypeEncoding(method); class_addMethod([self class], aSelector, (IMP)fireIMP, type);
self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; } } return self;}
void fireIMP(RYTimer *timer){ if (timer.target) { void (*ry_msgSend)(void *,SEL, id) = (void *)objc_msgSend; ry_msgSend((__bridge void *)(timer.target), timer.aSelector,timer.timer); }else{ [timer invalidate]; }}
- (void)invalidate{ [self.timer invalidate]; self.timer = nil;}
- (void)dealloc{ NSLog(@"%s",__func__);}
@end
2.2 消息转发机制
我们同样可以利用消息转发机制来对 Timer 进行优化,这里的思想和上面一样,都是使用了一层间接,但是具体实现方式不同。这里使用了 NSProxy 结合消息转发实现。
@interface RYProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface RYProxy ()
@property (nonatomic, weak) id object;
@end
@implementation RYProxy
+ (instancetype)proxyWithTransformObject:(id)object { RYProxy *proxy = [RYProxy alloc]; proxy.object = object; return proxy;}
-(id)forwardingTargetForSelector:(SEL)aSelector { return self.object;}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{ if (self.object) { } return [self.object methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation{ if (self.object) { [invocation invokeWithTarget:self.object]; }}
@end虽然 Target 并不是 Self 但经过消息转发,最终处理消息的还是消息的还是 Self 。
RYProxy *timerProxy = [RYProxy proxyWithTransformObject:self];self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:timerProxy selector:@selector(countAction) userInfo:nil repeats:YES];