本次代码实践源自一次面试.
Q:平时使用定时器NSTimer时如何释放应用它的target?
A:在viewDidDisappear中将调用invalid方法,在将timer置为nil.
但面试官想要的好像不是这个回答.
下面是我自己的答案的代码实践
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:@selector(timeAction:)
userInfo:nil
repeats:YES];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
上面代码self应用了timer,而timer页应用了self.这就形成了一个互相应用,肯定会有内存泄漏的.
我在dealloc方法打了断点,当页面消失后,的确会走到dealloc方法,说明这样做是可以释放当前self的.猜测应该是viewDidDisappear中的操作断开了timer对self的应用,从而内存得以释放.
下面是面试官给的思路
上面写的代码导致内存释放不了的就是self和timer之间互相应用了,想要self的释放不受timer影响就必须断开二者的互相应用.
关键点在于开启定时器时的target设置
我们定义一个中间件作为定时器的target,利用delegate回调定时器事件,由于delegate的弱应用,就实现了timer对self的弱应用.
//DetailViewController.m
#import "DetailViewController.h"
#import "Middleware.h"
@interface DetailViewController ()<MiddlewareDelegate>
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) Middleware *middleware;
@end
@implementation DetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.middleware = [[Middleware alloc] initWithDelegate:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2
target:self.middleware
selector:@selector(timeAction:)
userInfo:nil
repeats:YES];
}
- (void)dealloc {
}
- (void)delegateTimerAction {
NSLog(@"%f",CACurrentMediaTime());
}
@end
//Middleware.m
#import "Middleware.h"
@interface Middleware()
@property (nonatomic, weak) id<MiddlewareDelegate> delegate;
@end
@implementation Middleware
- (instancetype)initWithDelegate:(id<MiddlewareDelegate>)delegate {
self = [super init];
if (self) {
self.delegate = delegate;
}
return self;
}
- (void)timeAction:(NSTimer *)timer {
[self.delegate delegateTimerAction];
}
@end
当我们推出当前页面时,dealloc方法也会被调用,说明当前类被释放了.
从iOS10开始,苹果新增了3个创建定时器的方法用于避免循环引用
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
- (instancetype)initWithFireDate:(NSDate *)date
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
block:(void (^)(NSTimer *timer))block;
这三个方法都将定时器要执行的操作放在了block中,并且将当前timer作为block的参数传进去了.
思考
对于第一种方法有个问题,如果不是推出当前页面,而push到另一个页面,每次回来都要销毁创建创建销毁.
对于平时使用的api,只是简单的会用,没有深入去理解内在的机制.以后要多去了解内在机制、原理.