iOS中腌巾,常用的定時器有三種:NSTimer,CADisplayLink立叛,GCD负敏。在一定基礎(chǔ)之上,做進一步探究秘蛇。
NSTimer,CADisplayLink
在使用scheduleTimerWithTimeInterval:target:selector:userInfo:repeats:方式創(chuàng)建的定時器會以默認方式添加到當前線程runloop中顶考,無需手動添加赁还。
如果有需求:點擊屏幕觸發(fā)定時器,不需要時點擊返回
@interface TestViewController ()
@property(nonatomic,strong)NSTimer *timer;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
-(void)run
{
NSLog(@"%s",__func__);
}
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
@end
經(jīng)測試驹沿,這段代碼dealloc方法不會執(zhí)行艘策,存在循環(huán)引用。因為NSTimer內(nèi)部有強指針target渊季,所以不管外部傳入的是weak還是strong朋蔫,都會將傳入的內(nèi)存地址賦值給形參target,所以不管target存儲的是weak的地址還是strong的地址却汉,NSTimer對target都是強引用驯妄,所以timer和self產(chǎn)生了循環(huán)引用。
通過GNUStep中源碼可以看出合砂,傳入的object被retain一次青扔,被timer強持有。
代碼所產(chǎn)生的循環(huán)引用
要想解決這個循環(huán)引用翩伪,使其中一個強引用變成弱引用微猖。
- 方法一:使用block創(chuàng)建方式
block存儲在timer中,timer對block強引用缘屹,block對self是弱引用凛剥,定時器對self是弱引用。
__weak typeof(self)wself = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[wself run];
}];
- 方法二
如果使用scheduleTimerWithTimeInterval:target:方法轻姿,就要引入中間變量犁珠,將中間對象的指針置為弱指針逻炊。
@interface SSProxy : NSObject
//弱指針,打破循環(huán)引用
@property(nonatomic, weak)id target;
+(instancetype)proxyWithTarget:(id)target;
@end
@implementation SSProxy
+(instancetype)proxyWithTarget:(id)target
{
SSProxy *proxy = [[SSProxy alloc] init];
proxy.target = target;
return proxy;
}
//消息轉(zhuǎn)發(fā)階段盲憎,返回值不為空嗅骄,直接給某個對象發(fā)送消息
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
@interface TestViewController ()
@property(nonatomic,strong)NSTimer *timer;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[SSProxy proxyWithTarget:self] selector:@selector(run) userInfo:nil repeats:YES];
}
-(void)run
{
NSLog(@"%s",__func__);
}
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
@end
- 方法三:使用CADisplayLink
@interface TestViewController ()
@property(nonatomic,strong)CADisplayLink *timer;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.timer = [CADisplayLink displayLinkWithTarget:[SSProxy proxyWithTarget:self] selector:@selector(run)];
[self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
-(void)run
{
NSLog(@"%s",__func__);
}
-(void)dealloc
{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
@end
存在的弊端:
1.NSTimer和CADisplayLink底層是runloop實現(xiàn)的,所以有可能并不準時饼疙,如果runloop任務(wù)過于繁重溺森,每一圈的處理就會耗時,就導致不準時窑眯。
2.對于CADisplayLink屏积,當CPU忙于其他計算,就無法保證每秒60次的頻率執(zhí)行屏幕繪制磅甩。
GCD定時器
GCD定時器直接和系統(tǒng)內(nèi)核掛鉤炊林,不依賴于runloop,相對精準卷要。
@interface TestViewController ()
@property(nonatomic,strong)dispatch_source_t timer;
@end
@implementation TestViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_source_t timer = dispatch_source_create(&_dispatch_source_type_timer, 0, 0, queue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_resume(timer);
self.timer = timer;
}
-(void)run
{
NSLog(@"%s",__func__);
}
-(void)dealloc
{
NSLog(@"%s",__func__);
}
@end