一、NSTimer
1.基本特点
-
NSTimer
需要加入RunLoop
中,精度最低,用于执行一般定时任务。 - 比如 NSTimer 的触发时间到的时候,如果 RunLoop 在阻塞状态,触发时间就会推迟到下一个 RunLoop 周期。
- 最好加入到 RunLoop 的
common
中,避免被 UI 页面拖拽所暂停
2. 用法一 block
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@property(nonatomic, assign) int age;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.age = 10;
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"age is age %d", weakSelf.age);
}];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
- 注意点:需要用
weakSelf
避免timer
和self
循环引用,容易导致dealloc
方法不被调用,从而内存泄露。
3. 用法二 target,思考如下代码有问题吗?
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
- 会,
dealloc
方法并不会调用,会导致循环引用。 -
self
强引用了timer
,timer
强引用了self
。 - 如何解决这个问题呢?请思考,并往下阅读。
4. 用法二 target,可能有人会这么想,如下代码能解决循环引用吗?
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:weakSelf selector:@selector(task) userInfo:nil repeats:YES];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
- 并不能解决循环引用
- 这么想有一个严重的
概念错套
的问题 -
weakSelf
本质还是执行self
的指针,通常用于block 内部
解决循环引用
,因为block 内部
对weakSelf
和self
的捕获
做了区别对待。 - 上面的代码使用
weakSelf
和 使用self
没有任何区别,都会产生强引用
。
5. 用法二 target,viewWillDisappear
手动断环思路
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
- 既然循环引用会导致 dealloc 无法调用,那么我们在
viewWillDisappear
进行手动断环即可。
6. 用法二 target,代理断环(代理继承自 NSObject)
代理断环原理图- 我们无法避免 Timer 强引用 target,所以我们只能不让 self 当 NSTimer 的 target,我们找个代理,这样就能破除循环引用了。
#import "YYObjectProxy.h"
@interface YYObjectProxy ()
@property(nonatomic, weak)id target;
@end
@implementation YYObjectProxy
+ (instancetype)proxyWithTarget:(id)target {
YYObjectProxy *proxy = [[YYObjectProxy alloc] init];
proxy.target = target;
return proxy;
}
// 消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
- 调用代码(其他代码不变)
#import "ViewController.h"
#import "YYObjectProxy.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYObjectProxy *proxy = [YYObjectProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(task) userInfo:nil repeats:YES];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
7. 用法二 target,代理断环(代理继承自 NSProxy,也是暂时比较完美的解决方案)
我们使用继承自 NSObject 的代码固然能解决循环引用问题,但是上面代码还有更好的继承方案,那就是继承自 NSProxy。
-
NSProxy
非常特别,它本身也是一个基类,不继承自NSObject
- 对于
NSObject
的子类而言,会从当前类
和superclass
去找匹配的selector
;如果找不到,会进入消息动态解析
阶段;如果找不到,会进入消息转发
阶段 - 对于
NSProxy
的子类而言,会从当前类
去找匹配的selector
;如果找不到,会直接进入消息转发
阶段 -
NSProxy
的消息机制
会比NSObject
简单很多,相对来讲效率也高很多
// YYProxy.m
#import "YYProxy.h"
@interface YYProxy ()
@property(nonatomic, weak) id target;
@end
@implementation YYProxy
+(instancetype)proxyWithTarget:(id)target {
// NSProxy对象不需要调用init,因为它本来就没有init方法
YYProxy *proxy = [YYProxy alloc];
proxy.target = target;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
-(void)dealloc {
NSLog(@"%s", __func__);
}
@end
#import "ViewController.h"
#import "YYProxy.h"
@interface ViewController ()
@property(nonatomic, strong) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYProxy *proxy = [YYProxy proxyWithTarget:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:proxy selector:@selector(task) userInfo:nil repeats:YES];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.timer invalidate];
self.timer = nil;
}
@end
8. 结合上面实例7和实例6的代码,下面代码输出什么?
#import "ViewController.h"
#import "YYProxy.h"
#import "YYObjectProxy.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYProxy *proxy1 = [YYProxy proxyWithTarget:self];
YYObjectProxy *proxy2 = [YYObjectProxy proxyWithTarget:self];
NSLog(@"%d %d",
[proxy1 isKindOfClass:[ViewController class]],
[proxy2 isKindOfClass:[ViewController class]]
);
}
- 输出
1 0
- 因为
NSProxy
除开自定义的方法外,其他方法基本上都会转换到消息转发
机制进行调用,所以proxy1
本质上是在调用ViewController
的class
方法。
二、CDDisplayLink
1.基本特点
-
CDDisplayLink
需要加入RunLoop
中,精度中等,跟随屏幕刷新频率调用(大部分iOS设备是60FPS),主要用于和屏幕刷新有关的动画绘制
。
2.基本用法
- 创建代理
#import "YYProxy.h"
@interface YYProxy ()
@property(nonatomic, weak) id target;
@end
@implementation YYProxy
+(instancetype)proxyWithTarget:(id)target {
// NSProxy对象不需要调用init,因为它本来就没有init方法
YYProxy *proxy = [YYProxy alloc];
proxy.target = target;
return proxy;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
-(void)dealloc {
NSLog(@"%s", __func__);
}
@end
- 调用 CADisplayLink
#import "ViewController.h"
#import "YYProxy.h"
#import "YYObjectProxy.h"
@interface ViewController ()
@property(nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYProxy *proxy = [YYProxy proxyWithTarget:self];
self.displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(task)];
self.displayLink.preferredFramesPerSecond = 1;
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)task {
NSLog(@"do tesk");
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.displayLink invalidate];
self.displayLink = nil;
}
@end
- 调用 CADisplayLink用法注意点基本和NSTimer使用一致
三、GCD定时器
1. 基本特点
- 是iOS所有定时器中
最精准
的定时器 -
GCD定时器
是和系统内核
挂钩的,和RunLoop
没有关系 -
GCD定时器
还可以自动创建线程,只要我们传入一个自创的队列就行了 - GCD 创建的东西,在 ARC 的环境下,都不需要我们销毁
2. 主要方法
主要方法介绍3. 代码演示(已封装成 CGD 定时器工具类)
- 要注意
封装的思想
,先从外部调用
来看需要提供哪些方法
,然后再去实现工具类内部代码
。 - 封装的时候,把要注意的点写成注释,一条条去实现,才不容易遗漏。
#import "YYTimer.h"
@implementation YYTimer
NSMutableDictionary *timerDictionary_;
dispatch_semaphore_t timerSemaphore_;
+(void)initialize{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timerDictionary_ = [NSMutableDictionary dictionary];
timerSemaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)executeTask:(taskBlock)task start:(uint64_t)startInSeconds interval:(uint64_t)intervalInSeconds repeat:(BOOL)repeat async:(BOOL)async {
// 创建timer
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : 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(DISPATCH_TIME_NOW, startInSeconds * NSEC_PER_SEC), intervalInSeconds * NSEC_PER_SEC, 0);
dispatch_semaphore_wait(timerSemaphore_, DISPATCH_TIME_FOREVER);
// 创建唯一标识
static int counter = 0;
NSString *timerName = [NSString stringWithFormat:@"%d",counter++];
// 放入全局字典中
[timerDictionary_ setObject:timer forKey:timerName];
dispatch_semaphore_signal(timerSemaphore_);
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeat) {
[YYTimer cancelTimer:timerName];
}
});
dispatch_resume(timer);
return timerName;
}
+ (void)cancelTimer:(NSString *)timerName {
if (timerName.length == 0) return;
dispatch_semaphore_wait(timerSemaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = [timerDictionary_ objectForKey:timerName];
if (timer) {
[timerDictionary_ removeObjectForKey:timerName];
}
dispatch_semaphore_signal(timerSemaphore_);
}
@end
#import <Foundation/Foundation.h>
typedef void(^taskBlock)(void);
@interface YYTimer : NSObject
+ (NSString*)executeTask:(taskBlock)task
start:(uint64_t)startInSeconds
interval:(uint64_t)intervalInSeconds
repeat:(BOOL)repeat
async:(BOOL)async;
+ (void)cancelTimer:(NSString *)timerName;
@end
- 调用代码
#import "ViewController.h"
#import "YYTimer.h"
@interface ViewController ()
@property(nonatomic, strong) NSString *timerName;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timerName = [YYTimer executeTask:^{
NSLog(@"112%@",[NSThread currentThread]);
} start:2.0 interval:1.0 repeat:YES async:YES];
}
- (void)dealloc {
NSLog(@"%s", __func__);
[YYTimer cancelTimer:self.timerName];
}
@end
网友评论