前言
本文主要讲解3种计时器的比较和应用方式,从runloop的理解描述开始
附上学习中的代码:
学习过程
1.RunLoop的认识
2.三种计时器的应用方式&&比较
3.总结
内容
1.RunLoop的认识(此节主要是简要总结网上描述的认识)
thread–runloop–mode–event sources,关系可以表示如下:
内循环如下
理解:1.如同C语言中的 while(1),是程序的生命力,2.主线程会自动启动,3.子线程需要自己启动,4.不允许创建,子线程在获取它时隐式创建,线程结束时销毁5.同一时间只能启动一种模式,6.runloop记录在一个全局的字典中
获取函数CFRunloopGetCurrent(void)函数获取函数
获取当前线程的runloop使用以下代码
[NSRunLoop currentRunLoop];
可以添加观察者监听线程runloop,代码如下:
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer---%@", weakSelf.testTimer );
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理input Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"从睡眠中唤醒,处理完唤醒源之前");
break;
case kCFRunLoopExit:
NSLog(@"退出");
break;
default:
break;
}
});
CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], observer, kCFRunLoopCommonModes);
备注:如果不是监听主线程runloop,记得要启动runloop并且给线程runloop加入timer或者事件源
2.三种计时器的应用方式&&比较
当前有3种计时器的方案
1.NSTimer(最普通的计时器)
2.CADisplayLink(屏幕刷新频率)
3.dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
3种计时器的比较截图(NSTimer和CADisplayLink加入runloop的NSRunLoopCommonModes模式)
相比较,NSTimer准确率最低,CADisplayLink和dispatch方式生成的计时器相对准确。
3种计时器使用的注意事项
一、NSTimer和CADisplayLink的target不能直接设置为self,会互相引用导致计时器和对应的控制器不释放。问题很严重!!!
处理方案:借鉴SDWebImage库的方式,代理指向可解决问题,以下是SDWeakProxy的内部代码,使用例子请看下面内容
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[SDWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
例子:
SDWeakProxy *weakProxy = [SDWeakProxy proxyWithTarget:self];
self.testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakProxy selector:@selector(_recordTimer) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.testTimer forMode:NSRunLoopCommonModes];
理解:weakProxy是一个对象,这个对象指向了self!但是不是直接self引用了timer!!可以po以下weakProxy查看!!
二、3个计数器如果是控制器持有,dismiss时或者pop时最好置nil,dispatch方式的,最好能调用一下方法 dispatch_source_cancel(self.connectTimer); 保证及时释放!!!
总结
1.理解RunLoop的特性,适当时候可以使用runloop的观察者处理问题
2.理解3种定时器的使用,CALinkDisplay和dispatch_source_create方式较好,多用后者!因为NSTimer受Runloop处理事情的时候会有误差!!!