『ios』YYTimer 源码分析
2018-08-23 本文已影响27人
butterflyer
yy系列一直都在用,最近打算抽出时间来仔细阅读下大神的源码。因为今天第一眼就看到yytimer了,所以第一个由他开始吧。
首先看介绍
YYTimer is a thread-safe timer based on GCD. It has similar API with `NSTimer`.
YYTimer object differ from NSTimer in a few ways:
* It use GCD to produce timer tick, and won't be affected by runLoop.
* It make a weak reference to the target, so it can avoid retain cycles.
* It always fire on main thread.
翻译来,就是类似于nstimer的api,由gcd生成的线程安全的api.
不受runloop影响,target是由weak修饰,不会引起循环引用,在主线程。
先看.h文件
@interface YYTimer : NSObject
+ (YYTimer *)timerWithTimeInterval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector
repeats:(BOOL)repeats;//构造方法
- (instancetype)initWithFireTime:(NSTimeInterval)start
interval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector
repeats:(BOOL)repeats NS_DESIGNATED_INITIALIZER;
@property (readonly) BOOL repeats;//是否重复
@property (readonly) NSTimeInterval timeInterval;//时间间隔
@property (readonly, getter=isValid) BOOL valid;//
- (void)invalidate;
- (void)fire;
@end
继续往下面看
先从构造方法开始看
+ (YYTimer *)timerWithTimeInterval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector
repeats:(BOOL)repeats {
return [[self alloc] initWithFireTime:interval interval:interval target:target selector:selector repeats:repeats];
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"YYTimer init error" reason:@"Use the designated initializer to init." userInfo:nil];
return [self initWithFireTime:0 interval:0 target:self selector:@selector(invalidate) repeats:NO];
}
两个构造方法都指向下面的方法,所以这个方法才是重点
{
BOOL _valid;
NSTimeInterval _timeInterval;
BOOL _repeats;
__weak id _target; //弱引用
SEL _selector;
dispatch_source_t _source;
dispatch_semaphore_t _lock;
}
- (instancetype)initWithFireTime:(NSTimeInterval)start
interval:(NSTimeInterval)interval
target:(id)target
selector:(SEL)selector
repeats:(BOOL)repeats {
self = [super init];
_repeats = repeats;
_timeInterval = interval;
_valid = YES;
_target = target;
_selector = selector;
__weak typeof(self) _self = self;
_lock = dispatch_semaphore_create(1);//在这里创建了一个全局的信号量
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());//全局的source
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, (start * NSEC_PER_SEC)), (interval * NSEC_PER_SEC), 0); //创建时间计时器,这里应该就是不受runloop影响的原因
dispatch_source_set_event_handler(_source, ^{[_self fire];});//触发的事件
dispatch_resume(_source);//执行source
return self;
}
上面用到了gcd中的两个知识,dispatch_source和 信号量 dispatch_semaphore
之前在写时间循环的时候用过dispatch_source这个函数.
- (void)invalidate { //注销时间计时器
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
if (_valid) {
dispatch_source_cancel(_source);
_source = NULL;
_target = nil;
_valid = NO;
}
dispatch_semaphore_signal(_lock);
}
- (void)fire { //出发的事件
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); //这里用信号量我的想法是来保证线程安全
id target = _target;
if (!_repeats || !target) {
dispatch_semaphore_signal(_lock);
[self invalidate];
} else {
dispatch_semaphore_signal(_lock);
[target performSelector:_selector withObject:self];
}
#pragma clang diagnostic pop
}
.m文件中有个宏
define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
VA_ARGS;
dispatch_semaphore_signal(_lock);
针对信号量,形成锁的宏
- (NSTimeInterval)timeInterval {
LOCK(NSTimeInterval t = _timeInterval) return t;
}
- (BOOL)repeats {
LOCK(BOOL repeat = _repeats); return repeat;
}
其中对于VA_ARGS的解释
__VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的C99规范中新增的,目前似乎只有gcc支持(VC6.0的编译器不支持)。宏前面加上##的作用在于,当可变参数的个数为0时,这里的##起到把前面多余的","去掉的作用,否则会编译出错, 你可以试试
我的理解,这里的VA_ARGS,就是穿进去的赋值操作 NSTimeInterval t = _timeInterval 和
BOOL repeat = _repeats,目的是形成锁。