前两天被问到NSTimer的使用场景以及循环引用问题,回过头来作一些研究,记录一下。bash
页面1跳转页面2oop
#import "ViewController.h"
#import "OneViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor redColor];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//页面1跳转页面2
OneViewController *vc = [[OneViewController alloc] init];
[self presentViewController:vc animated:true completion:nil];
}
复制代码
页面2中有个定时器,经过scheduledTimerWithTimeInterval初始化,实现timer的方法,并在页面2的dealloc方法中释放timerui
#import "OneViewController.h"
@interface OneViewController ()
@property (nonatomic,strong) NSTimer *timer;//页面2中有一个定时器
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(timerAction)
userInfo:nil repeats:YES];
}
- (void)timerAction {
NSLog(@"正在计时...");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self dismissViewControllerAnimated:true completion:nil];
}
- (void)dealloc {
NSLog(@"dealloc");
[self.timer invalidate];
self.timer = nil;
}
复制代码
页面2触发dismiss方法后dealloc方法并无被调用,说明页面没有被释放,timer也没有被释放,虽然页面已经关闭,定时器方法timerAction仍不断被调用this
思考:
页面2没有走dealloc方法说明没有被释放,不断调用timerAction方法说明timer也没有被释放,考虑瑟吉是由于页面2对timer有强引用,timer也对页面2有强引用,将timer的属性设为weak,尝试事后结果仍然发生着循环引用; 或者设置__weak typeof(self) weakSelf = self,结果仍是同样atom
关于NSTimer中target文档描述:
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated
可知timer对target会有一个强引用,直到timer失效(invalidate)spa
timer要想解除对target的强引用,须要先invalidate,这就形成页面2的dealloc方法不被调用,进而没法invalidate,因此循环引用一直保持code
大概有如下几种办法解除这个问题orm
@interface WeakTarget : NSObject
@property (nonatomic,assign) SEL selector;
@property (nonatomic,weak) NSTimer *timer;
@property (nonatomic,weak) id target;
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(nonnull id)aTarget
selector:(nonnull SEL)aSelector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo;
@end
复制代码
@implementation WeakTarget
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)yesOrNo
{
WeakTarget *weakTarget = [[WeakTarget alloc] init];
weakTarget.target = aTarget; // aTarget = OneViewController
weakTarget.selector = aSelector; // aSelector = timerAction方法的SEL包装
// weakTarget.timer对weakTarget有一个强引用
weakTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:weakTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:yesOrNo];
return weakTarget.timer;
}
-(void)fire:(NSTimer *)timer {
if (self.target) {
//调用了外界传来的selector,即timerAction方法
//由OneViewController调用本身的timerAction方法
//这里会产生一个警告:PerformSelector may cause a leak because its selector is unknown.
[self.target performSelector:self.selector withObject:timer.userInfo];
}else {
[self.timer invalidate];
}
}
@end
复制代码
外部调用:cdn
#import "OneViewController.h"
#import "WeakTarget.h"
@interface OneViewController ()
@property (nonatomic,strong) NSTimer *timer;//页面2
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor greenColor];
self.timer = [WeakTarget scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
}
复制代码
@interface WeakTarget1 : NSObject
+(instancetype)initWithTarget:(id)target;
@end
复制代码
@interface WeakTarget1()
@property (nonatomic,weak) id target;
@end
@implementation WeakTarget1
+(instancetype)initWithTarget:(id)target {
WeakTarget1 *weakTarget = [[WeakTarget1 alloc] init];
weakTarget.target = target;
return weakTarget;
}
//为了保证中间件能响应外部self的事件,须要经过消息转发机制,让实际的响应target仍是外部的self
// 消息转发,简单来讲就是若是当前对象没有实现这个方法,系统会到这个方法里来找实现对象。
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
复制代码
外部调用:中间件
#import "OneViewController.h"
#import "WeakTarget1.h"
@interface OneViewController ()
@property (nonatomic,strong) NSTimer *timer;//页面2
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor greenColor];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:[WeakTarget1 initWithTarget:self]
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
}
复制代码
@interface OneViewController ()
@property (nonatomic,strong) NSTimer *timer;//页面2
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor greenColor];
/*
为了不在 block 的执行过程当中,忽然出现 self 被释放的状况,先定义了一个弱引用,令其指向self,
而后使block捕获这个引用,而不直接去捕获普通的self变量。也就是说,self不会为计时器所保留。
当block开始执行时,马上生成strong引用,以保证明例在执行期间持续存活。
*/
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(self)strongSelf = weakSelf;
[strongSelf timerAction];
}];
复制代码
@interface NSTimer (block)
+(NSTimer *)zq_scheduledTimerIntervl:(NSTimeInterval)interval
block:(void(^)(void))block
repeats:(BOOL)repeats;
@end
复制代码
#import "NSTimer+block.h"
@implementation NSTimer (block)
+ (NSTimer *)zq_scheduledTimerIntervl:(NSTimeInterval)interval block:(void (^)(void))block repeats:(BOOL)repeats {
/*userInfo文档描述:The user info for the timer.
The timer maintains a strong reference to this object until it (the timer) is invalidated.
This parameter may be nil.
计时器的用户信息。计时器保持对该对象的强引用,直到它(计时器)失效。此参数能够为nil。
由于如今是假定iOS10以前的系统版本,timer没有自带block参数的实例化方法,手动实现,将block做为timer的信息赋值给userInfo
*/
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(zq_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+(void)zq_blockInvoke:(NSTimer *)timer {
//在timer方法中执行block
void (^block)(void) = timer.userInfo;
if (block) {
block();
}
}
复制代码
外部调用:
#import "OneViewController.h"
#import "NSTimer+block.h"
@interface OneViewController ()
@property (nonatomic,strong) NSTimer *timer;//页面2
@end
@implementation OneViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor greenColor];
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer zq_scheduledTimerIntervl:2.0 block:^{
__strong typeof(self)strongSelf = weakSelf;
[strongSelf timerAction];
} repeats:YES];
}
复制代码
2019-08-31 15:18:08.924239+0800 NSTimer相关[65510:4082744] 正在计时...
2019-08-31 15:18:11.376765+0800 NSTimer相关[65510:4082744] dealloc
2019-08-31 15:18:11.376948+0800 NSTimer相关[65510:4082744] 已经失效
当timer设置为repeats = NO时候,不会存在上述问题
repeats参数文档描述:
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.
若是设置为YES,计时器将不断循环,直到无效。若是设置为NO,计时器将在触发后失效。(至关于设置为NO时自动调用invalidate方法)
-(void)invalidate方法文档解释: This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point. If it was configured with target and user info objects, the receiver removes its strong references to those objects as well. 此方法是从NSRunLoop对象中删除计时器的惟一方法。在invalidate方法返回以前或稍后某个时间点,NSRunLoop对象将删除对计时器的强引用 若是有的话,定时器的target和userInfo对象的强引用也会被一块儿删除