iOS中三種定時(shí)器CADisplayLink侣背、NSTimer和GCD定時(shí)器,我們就詳盡的講解一下三種定時(shí)器.
在講解CADisplayLink、NSTimer的時(shí)候我們需要對(duì)于runloop有一部分的了解:iOS RunLoop(1)-底層解析.
1. NSTimer
NSTimer我相信在大家開發(fā)的過程中肯定都用過,但是NSTimer還是有一些需要注意的地方:iOS RunLoop(2)-應(yīng)用.下面我們?cè)賮砗?jiǎn)單的介紹一下NSTimer的使用.
下面這兩種形式不需要添加runloop,因?yàn)槭莝cheduledTimerWithTimeInterval方式創(chuàng)建的.
\\NSTimer的創(chuàng)建方式
\\普通的創(chuàng)建方式
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
/**
NSTimer
@param NSTimeInterval 等待時(shí)間(單位是秒堤瘤,即就是每個(gè)幾秒執(zhí)行一次)
@param target 執(zhí)行對(duì)象
@param selector 執(zhí)行方法(定時(shí)器執(zhí)行的方法)
@param userInfo 標(biāo)識(shí)信息(一般不用)
@param repeats 是否重復(fù)執(zhí)行
@return NSTimer對(duì)象(定時(shí)器)
*/
\\block的方式創(chuàng)建
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
//重復(fù)執(zhí)行的代碼
}];
/**
NSTimer block的創(chuàng)建方式
@param TimerInterval 等待時(shí)間
@param repeats 是否重復(fù)執(zhí)行
@param block 執(zhí)行的代碼
@return NSTimer對(duì)象
*/
下面這幾種創(chuàng)建的方式是需要添加runloop的
\\第一種創(chuàng)建方式
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
\\第二種創(chuàng)建方式
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"-----");
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
\\第三種創(chuàng)建方式
NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
\\第四種創(chuàng)建方式
NSTimer* timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"----");
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
2. CADisplayLink
? ?? CADisplayLink可能大家很少用過,CADisplayLink的功能很單一,就是作為一個(gè)定時(shí)器的存在浆熔。他的優(yōu)勢(shì)就在于他的執(zhí)行頻率是根據(jù)設(shè)備屏幕的刷新頻率來計(jì)算的本辐。換句話講,他也是時(shí)間間隔最準(zhǔn)確的定時(shí)器医增。
? ?? CADisplayLink是一個(gè)能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫到屏幕上的定時(shí)器慎皱。我們?cè)趹?yīng)用中創(chuàng)建一個(gè)新的 CADisplayLink 對(duì)象,把它添加到一個(gè)runloop中叶骨,并給它提供一個(gè) target 和 selector 在屏幕刷新的時(shí)候調(diào)用茫多。
? ?? 一但 CADisplayLink 以特定的模式注冊(cè)到runloop之后,每當(dāng)屏幕需要刷新的時(shí)候忽刽,runloop就會(huì)調(diào)用CADisplayLink綁定的target上的selector天揖,這時(shí)target可以讀到 CADisplayLink 的每次調(diào)用的時(shí)間戳,用來準(zhǔn)備下一幀顯示需要的數(shù)據(jù)跪帝。例如一個(gè)視頻應(yīng)用使用時(shí)間戳來計(jì)算下一幀要顯示的視頻數(shù)據(jù)今膊。在UI做動(dòng)畫的過程中,需要通過時(shí)間戳來計(jì)算UI對(duì)象在動(dòng)畫的下一幀要更新的大小等等伞剑。
? ?? 在添加進(jìn)runloop的時(shí)候我們應(yīng)該選用高一些的優(yōu)先級(jí)斑唬,來保證動(dòng)畫的平滑。可以設(shè)想一下赖钞,我們?cè)趧?dòng)畫的過程中,runloop被添加進(jìn)來了一個(gè)高優(yōu)先級(jí)的任務(wù)聘裁,那么雪营,下一次的調(diào)用就會(huì)被暫停轉(zhuǎn)而先去執(zhí)行高優(yōu)先級(jí)的任務(wù),然后在接著執(zhí)行CADisplayLink的調(diào)用衡便,從而造成動(dòng)畫過程的卡頓献起,使動(dòng)畫不流暢。
CADisplayLink 不能被繼承.
給非UI對(duì)象添加動(dòng)畫效果:
? ?? 我們知道動(dòng)畫效果就是一個(gè)屬性的線性變化镣陕,比如 UIView 動(dòng)畫的 EasyIn EasyOut 谴餐。通過數(shù)值按照不同速率的變化我們能生成更接近真實(shí)世界的動(dòng)畫效果。我們也可以利用這個(gè)特性來使一些其他屬性按照我們期望的曲線變化呆抑。比如當(dāng)播放視頻時(shí)關(guān)掉視頻的聲音我可以通過 CADisplayLink 來實(shí)現(xiàn)一個(gè) EasyOut 的漸出效果:先快速的降低音量岂嗓,在慢慢的漸變到靜音。
注意
通常來講:iOS設(shè)備的刷新頻率事60HZ也就是每秒60次鹊碍。那么每一次刷新的時(shí)間就是1/60秒 大概16.7毫秒厌殉。當(dāng)我們的frameInterval值為1的時(shí)候我們需要保證的是 CADisplayLink調(diào)用的target的函數(shù)計(jì)算時(shí)間不應(yīng)該大于 16.7否則就會(huì)出現(xiàn)嚴(yán)重的丟幀現(xiàn)象。 在mac應(yīng)用中我們使用的不是CADisplayLink而是 CVDisplayLink它是基于C接口的用起來配置有些麻煩但是用起來還是很簡(jiǎn)單的侈咕。
CADisplayLink的創(chuàng)建方法:
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
參數(shù)的解析:
? ??duration屬性:提供了每幀之間的時(shí)間公罕,也就是屏幕每次刷新之間的的時(shí)間。該屬性在target的selector被首次調(diào)用以后才會(huì)被賦值耀销。selector的調(diào)用間隔時(shí)間計(jì)算方式是:時(shí)間=duration×frameInterval楼眷。 我們可以使用這個(gè)時(shí)間來計(jì)算出下一幀要顯示的UI的數(shù)值。但是 duration只是個(gè)大概的時(shí)間熊尉,如果CPU忙于其它計(jì)算罐柳,就沒法保證以相同的頻率執(zhí)行屏幕的繪制操作,這樣會(huì)跳過幾次調(diào)用回調(diào)方法的機(jī)會(huì)帽揪。
? ?? frameInterval屬性:是可讀可寫的NSInteger型值硝清,標(biāo)識(shí)間隔多少幀調(diào)用一次selector 方法,默認(rèn)值是1转晰,即每幀都調(diào)用一次芦拿。如果每幀都調(diào)用一次的話,對(duì)于iOS設(shè)備來說那刷新頻率就是60HZ也就是每秒60次查邢,如果將 frameInterval 設(shè)為2 那么就會(huì)兩幀調(diào)用一次蔗崎,也就是變成了每秒刷新30次。
? ?? pause屬性:控制CADisplayLink的運(yùn)行扰藕。當(dāng)我們想結(jié)束一個(gè)CADisplayLink的時(shí)候缓苛,應(yīng)該調(diào)用-(void)invalidate 從runloop中刪除并刪除之前綁定的 target 跟 selector。
? ?? timestamp屬性: 只讀的CFTimeInterval值,表示屏幕顯示的上一幀的時(shí)間戳未桥,這個(gè)屬性通常被target用來計(jì)算下一幀中應(yīng)該顯示的內(nèi)容笔刹。 打印timestamp值,其樣式類似于:179699.631584冬耿。
3. CADisplayLink和NSTimer循環(huán)引用
demo
@interface ViewController ()
@property (strong , nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//保證調(diào)用頻率和屏幕的刷幀頻率一致,60fps
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void) linkTest{
NSLog(@"%s",__func__);
}
@end
@interface ViewController ()
@property (strong , nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void) timerTest{
NSLog(@"%s",__func__);
}
@end
上述代碼舌菜,將創(chuàng)建一個(gè)無限循環(huán)的 timer,但是無論是NSTimer還是CADisplayLink都會(huì)造成循環(huán)引用.
我們以timer的代碼作分析:
在這個(gè)方法中,如果repeats
傳入的參數(shù)是YES
亦镶,那么NSTimer
對(duì)象會(huì)保留target
傳入的對(duì)象日月,也就是強(qiáng)持有它,這種情形會(huì)保持到NSTimer
對(duì)象失效為止缤骨。如果是一次性的定時(shí)器爱咬,不需要手動(dòng)去invalidate
就會(huì)失效,但是重復(fù)性的定時(shí)器绊起,需要主動(dòng)去調(diào)用invalidate
方法才會(huì)失效精拟。
綜上看來,viewController
強(qiáng)持有了NSTimer
對(duì)象虱歪,而NSTimer
對(duì)象在其[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeStart) userInfo:nil repeats:YES];
方法中對(duì)傳入的target
參數(shù)也進(jìn)行了強(qiáng)引用串前,這也保證了NSTimer
對(duì)象調(diào)用時(shí)的正確性。此時(shí)如果傳入target
的是self
实蔽,那么就會(huì)形成viewController
和NSTimer
對(duì)象的循環(huán)引用環(huán)荡碾,如果循環(huán)引用環(huán)沒有被打破,那么viewController
在彈棧的時(shí)候就不會(huì)被釋放局装,也就不會(huì)走dealloc
方法坛吁,也就不會(huì)將NSTimer
對(duì)象invalidate
,這個(gè)時(shí)候viewController
和NSTimer
對(duì)象都得不到釋放铐尚,也就產(chǎn)生了內(nèi)存泄漏拨脉。
一. 那么這種循環(huán)引用的問題我們?nèi)绾谓鉀Q呢?
①. 第一種解決辦法
我們可能想如果我們將weak去弱化self,是不是就可以解決循環(huán)引用的問題了.
_weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:weakSelf
selector:@selector(timerFire)
userInfo:nil
repeats:YES];
然而這并沒有什么卵用,這里的 __weak 和 __strong 唯一的區(qū)別就是:如果在這兩行代碼執(zhí)行的期間 self 被釋放了宣增, NSTimer 的 target 會(huì)變成 nil 玫膀。但是如果我們用blcok的形式去創(chuàng)建NSTimer就可以用對(duì)self的弱化來解決了.
②. 第二種解決辦法
既然沒辦法通過 __weak 把 self 抽離出來,我們可以造個(gè)假的 target 給 NSTimer 爹脾。這個(gè)假的 target 類似于一個(gè)中間的代理人帖旨,它做的唯一的工作就是挺身而出接下了 NSTimer 的強(qiáng)引用。實(shí)現(xiàn)方式如下:
@interface MJProxy : NSObject
+ (instancetype)proxyWithTarget: (id)target;
@property (weak, nonatomic) id target;
@end
@implementation MJProxy
+ (instancetype)proxyWithTarget: (id)target{
MJProxy *proxy = [[MJProxy alloc] init];
proxy.target = target;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
@end
調(diào)用的時(shí)候是這樣調(diào)用的
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void) timerTest{
NSLog(@"%s",__func__);
}
3. NSProxy
NSProxy
是一個(gè)虛類.OC中類是不支持多繼承的灵妨,要想實(shí)現(xiàn)多繼承一般是有protocol的方式解阅,還有一種就是利用NSProxy
。有同學(xué)可能會(huì)問為什么不用NSObject來做泌霍?同樣都是基類货抄,都支持NSObject協(xié)議,NSProxy 有的NSObject 都有。但是點(diǎn)進(jìn)NSProxy .h可以看見NSProxy沒有init方法蟹地,而且NSProxy自身的方法很少积暖,是一個(gè)很干凈的類。這點(diǎn)很重要怪与,因?yàn)镹SObject自身的分類特別多呀酸,而消息轉(zhuǎn)發(fā)的機(jī)制是當(dāng)接收者無法處理時(shí)才會(huì)通過forwardInvocation:來尋求能夠處理的對(duì)象.在日常使用時(shí),我們很難避免不使用NSObject 的分類方法比如valueForKey這個(gè)方法NSObject就不會(huì)轉(zhuǎn)發(fā)琼梆。
一. 多繼承
虛基類本身就是為了解決繼承問題而生,而OC的動(dòng)態(tài)特性使得我們可以利用NSProxy做多繼承:
首先我們新建兩個(gè)基類如下:
@implementation classA
-(void)infoA{
NSLog(@"這里 是 classA");
}
@end
@implementation classB
-(void)infoB{
NSLog(@"這里 是 classB");
}
@end
classA和classB窿吩,但是分開買太麻煩茎杂,所以我找了一個(gè)代理如下:
@interface ClassProxy : NSProxy
@property(nonatomic,strong,readonly)NSMutableArray *targetArray;
-(void)target:(id)target;
-(void)handleTargets:(NSArray *)targets;
@end
NSProxy 必須以子類的形式出現(xiàn)。
因?yàn)榭紤]到很可能還有其他的類classC纫雁,classC也需要ClassProxy來代理煌往,這邊做了一個(gè)數(shù)組來存放需要代理的類.
@interface ClassProxy()
@property(nonatomic,strong)NSMutableArray *targetArray;//多個(gè)targets皆可代理
@property(nonatomic,strong)NSMutableDictionary *methodDic;
@property(nonatomic,strong)id target;
@end
然后target 和 相對(duì)應(yīng)的method demo做了一個(gè)字典來存儲(chǔ),方便獲取
-(void)registMethodWithTarget:(id)target{
unsigned int countOfMethods = 0;
Method *method_list = class_copyMethodList([target class], &countOfMethods);
for (int i = 0; i<countOfMethods; i++) {
Method method = method_list[i];
//得到方法的符號(hào)
SEL sel = method_getName(method);
//得到方法的符號(hào)字符串
const char *sel_name = sel_getName(sel);
//得到方法的名字
NSString *method_name = [NSString stringWithUTF8String:sel_name];
self.methodDic[method_name] = target;
}
free(method_list);
}
然后就是最主要的兩個(gè)方法
-(void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = invocation.selector;
NSString *methodName = NSStringFromSelector(sel);
id target = self.methodDic[methodName];
if (target) {
[invocation invokeWithTarget:target];
}
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *Method;
NSString *methodName = NSStringFromSelector(sel);
id target = self.methodDic[methodName];
if (target) {
Method = [target methodSignatureForSelector:sel];
}else{
Method = [super methodSignatureForSelector:sel];
}
return Method;
}
methodSignatureForSelector
:得到對(duì)應(yīng)的方法簽名轧邪,通過forwardInvocation
:轉(zhuǎn)發(fā)
下面看一下調(diào)用和打印結(jié)果:
[super viewDidLoad];
// [self analysis];
[self classInheritance];
// Do any additional setup after loading the view, typically from a nib.
}
//多繼承
-(void)classInheritance{
classA *A = [[classA alloc]init];
classB *B = [[classB alloc]init];
ClassProxy *proxy = [ClassProxy alloc];
[proxy handleTargets:@[A,B]];
[proxy performSelector:@selector(infoA)];
[proxy performSelector:@selector(infoB)];
}
2018-12-27 18:02:34.445 NSProxyStudy[18975:4587631] 這里 是 classA
2018-12-27 18:02:34.446 NSProxyStudy[18975:4587631] 這里 是 classB
以上就是利用NSProxy 實(shí)現(xiàn)多繼承刽脖。
二. 避免循環(huán)引用
這里舉了比較常見了一個(gè)例子NSTimer,(注意:NSProxy是沒有init方法).
由于目前蘋果在iOS10以上,已經(jīng)給出了timer 的block方式忌愚,已經(jīng)可以解決循環(huán)引用的問題曲管。所以demo舉例只是說明利用NSProxy如何解決循環(huán)引用,大家在使用的時(shí)候可直接使用系統(tǒng)的方法硕糊。
首先因?yàn)镹STimer創(chuàng)建的時(shí)候需要傳入一個(gè)target院水,并且持有它,而target本身也會(huì)持有timer所以會(huì)造成循環(huán)引用简十。所以我們將target 用NSProxy的子類代替如下:
@interface MJProxy : NSProxy
+ (instancetype)proxyWithTarget: (id)target;
@property (weak, nonatomic) id target;
@end
@implementation MJProxy
+ (instancetype)proxyWithTarget: (id)target{
MJProxy *proxy = [MJProxy alloc];
proxy.target = target;
return proxy;
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [self.target methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:self.target];
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void) timerTest{
NSLog(@"%s",__func__);
}
三. AOP面向切片編程
iOS中面向切片編程一般有兩種方式 檬某,一個(gè)是直接基于runtime 的method-Swizzling.還有一種就是基于NSProxy.
我們先創(chuàng)建一個(gè)子類AOPProxy:
typedef void(^proxyBlock)(id target,SEL selector);
NS_ASSUME_NONNULL_BEGIN
@interface AOPProxy : NSProxy
+(instancetype)proxyWithTarget:(id)target;
-(void)inspectSelector:(SEL)selector preSelTask:(proxyBlock)preTask endSelTask:(proxyBlock)endTask;
@end
-(void)inspectSelector:(SEL)selector preSelTask:(proxyBlock)preTask endSelTask:(proxyBlock)endTask;
第一個(gè)參數(shù)是需要hook的方法名字 后面兩個(gè)分別是hook 該方法后 執(zhí)行前需要執(zhí)行的block 和 執(zhí)行后的需要執(zhí)行的block.
@interface AOPProxy ()
@property(nonatomic,strong)id target;
@property(nonatomic,strong)NSMutableDictionary *preSelTaskDic;
@property(nonatomic,strong)NSMutableDictionary *endSelTaskDic;
@end
然后創(chuàng)建兩個(gè)字典,分別存放 不同selector 對(duì)應(yīng)的執(zhí)行block(可能一個(gè)target有好幾個(gè)方法需要被hook).
然后我們來看一下執(zhí)行:
-(void)inspect{
NSMutableArray *targtArray = [AOPProxy proxyWithTarget:[NSMutableArray arrayWithCapacity:1]];
[(AOPProxy *)targtArray inspectSelector:@selector(addObject:) preSelTask:^(id target, SEL selector) {
[target addObject:@"-------"];
NSLog(@"%@我加進(jìn)來之前",target);
} endSelTask:^(id target, SEL selector) {
[target addObject:@"-------"];
NSLog(@"%@我加進(jìn)來之后",target);
}];
[targtArray addObject:@"我是一個(gè)元素"];
}
2018-12-28 11:57:05.590 NSProxyStudy[23812:4840464] (
"-------"
)我加進(jìn)來之前
2018-12-28 11:57:05.591 NSProxyStudy[23812:4840464] (
"-------",
"\U6211\U662f\U4e00\U4e2a\U5143\U7d20",
"-------"
)我加進(jìn)來之后
4. GCD定時(shí)器
GCD定時(shí)器實(shí)際上是使用了dispatch源(dispatch source)螟蝙,dispatch源監(jiān)聽系統(tǒng)內(nèi)核對(duì)象并處理恢恼。dispatch類似生產(chǎn)者消費(fèi)者模式,通過監(jiān)聽系統(tǒng)內(nèi)核對(duì)象胰默,在生產(chǎn)者生產(chǎn)數(shù)據(jù)后自動(dòng)通知相應(yīng)的dispatch隊(duì)列執(zhí)行场斑,后者充當(dāng)消費(fèi)者。通過系統(tǒng)級(jí)調(diào)用牵署,更加精準(zhǔn)和簸。
NSTimer依賴于RunLoop,如果RunLoop的任務(wù)過于繁重碟刺,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí),而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí).
GCD定時(shí)器的使用:
//隊(duì)列創(chuàng)建
dispatch_queue_t queue = dispatch_get_main_queue();
//創(chuàng)建定時(shí)器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//設(shè)置時(shí)間
NSTimeInterval start = 2.0;
NSTimeInterval interval = 1.0;
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, start * NSEC_PER_SEC, interval * NSEC_PER_SEC);
//設(shè)置回調(diào)
dispatch_source_set_event_handler(timer, ^{
NSLog(@"111");
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);
GCD的定時(shí)器寫法相對(duì)比較固定锁保,這里建議封裝,封裝之前先理一理思路(GCD定時(shí)器的封裝必然依賴原有的寫法,所以原有的一些關(guān)鍵性的參數(shù)必須保留):
1、開始時(shí)間
2爽柒、時(shí)間間隔
3吴菠、是否需要重復(fù)執(zhí)行
4、是否支持多線程
5浩村、具體執(zhí)行任務(wù)的函數(shù)或者block做葵,其實(shí)這里兩種都是可以的
6、取消定時(shí)任務(wù)
上述就是簡(jiǎn)單的思路心墅,接下來先進(jìn)行簡(jiǎn)單的封裝:
@interface ZCGCDTimer : NSObject
+ (void)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer;
@end
@implementation ZCGCDTimer
+ (void)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
對(duì)參數(shù)做一些限制
1.如果task不存在酿矢,那就沒有執(zhí)行的必要(!task)
2.開始時(shí)間必須大于當(dāng)前時(shí)間
3.當(dāng)需要重復(fù)執(zhí)行時(shí),重復(fù)間隔時(shí)間必須 >0
以上條件必須滿足怎燥,定時(shí)器才算是比較合理瘫筐,否則沒必要執(zhí)行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return; (上面的代碼有人可能會(huì)寫成這樣,都一樣铐姚,這是if的語法策肝,里面只有一行時(shí)候可以省略{},其他的沒區(qū)別)
/**
隊(duì)列
async:YES 全局隊(duì)列 dispatch_get_global_queue(0, 0) 可以簡(jiǎn)單理解為其他線程(非主線程)
async:NO 主隊(duì)列 dispatch_get_main_queue() 可以理解為主線程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
創(chuàng)建定時(shí)器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定時(shí)任務(wù)
task();
//如果不需要重復(fù)隐绵,執(zhí)行一次即可
if (!repeats) {
dispatch_source_cancel(timer);
}
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);
}
+(void)canelTimer{
}
@end
當(dāng)我們想取消定時(shí)器的時(shí)候會(huì)思考之众,多個(gè)定時(shí)器如何取消呢(很多時(shí)候可能會(huì)創(chuàng)建不止一個(gè)定時(shí)器),得需要一個(gè)標(biāo)準(zhǔn)依许,或者說根據(jù)一個(gè)標(biāo)記取到對(duì)應(yīng)的定時(shí)器棺禾,這里我們根據(jù)key - value的形式進(jìn)行完善上述定時(shí)器,具體如下:
進(jìn)一步封裝(這里關(guān)于load、initialize的選擇抽時(shí)間再講峭跳,必要的話專門開一篇細(xì)說):
@interface ZCGCDTimer : NSObject
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer:(NSString*) timerName;
@end
@implementation ZCGCDTimer
static NSMutableDictionary *timers_;
/**
load 與 initialize區(qū)別帘睦,這里選用initialize
*/
+(void)initialize{
//GCD一次性函數(shù)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
對(duì)參數(shù)做一些限制
1.如果task不存在,那就沒有執(zhí)行的必要(!task)
2.開始時(shí)間必須大于當(dāng)前時(shí)間
3.當(dāng)需要重復(fù)執(zhí)行時(shí)坦康,重復(fù)間隔時(shí)間必須 >0
以上條件必須滿足竣付,定時(shí)器才算是比較合理,否則沒必要執(zhí)行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代碼有人可能會(huì)寫成這樣滞欠,都一樣古胆,這是if的語法,里面只有一行時(shí)候可以省略{}筛璧,其他的沒區(qū)別)
/**
隊(duì)列
async:YES 全局隊(duì)列 dispatch_get_global_queue(0, 0) 可以簡(jiǎn)單理解為其他線程(非主線程)
async:NO 主隊(duì)列 dispatch_get_main_queue() 可以理解為主線程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
創(chuàng)建定時(shí)器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 定時(shí)器的唯一標(biāo)識(shí)
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定時(shí)任務(wù)
task();
//如果不需要重復(fù)逸绎,執(zhí)行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);
return timerName;
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
}
@end
上面的代碼涉及到對(duì)字典的存取,所以還需要考慮到線程安全(只需要在存值取值的時(shí)候進(jìn)行處理)夭谤,因此棺牧,需要進(jìn)一步完善代碼,如下所示:
最終封裝:
@interface ZCGCDTimer : NSObject
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
+(void)canelTimer:(NSString*) timerName;
@end
@implementation ZCGCDTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
/**
load 與 initialize區(qū)別朗儒,這里選用initialize
*/
+(void)initialize{
//GCD一次性函數(shù)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
對(duì)參數(shù)做一些限制
1.如果task不存在颊乘,那就沒有執(zhí)行的必要(!task)
2.開始時(shí)間必須大于當(dāng)前時(shí)間
3.當(dāng)需要重復(fù)執(zhí)行時(shí)参淹,重復(fù)間隔時(shí)間必須 >0
以上條件必須滿足,定時(shí)器才算是比較合理乏悄,否則沒必要執(zhí)行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代碼有人可能會(huì)寫成這樣浙值,都一樣,這是if的語法檩小,里面只有一行時(shí)候可以省略{}开呐,其他的沒區(qū)別)
/**
隊(duì)列
async:YES 全局隊(duì)列 dispatch_get_global_queue(0, 0) 可以簡(jiǎn)單理解為其他線程(非主線程)
async:NO 主隊(duì)列 dispatch_get_main_queue() 可以理解為主線程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
創(chuàng)建定時(shí)器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定時(shí)器的唯一標(biāo)識(shí)
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_semaphore_signal(semaphore_);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定時(shí)任務(wù)
task();
//如果不需要重復(fù),執(zhí)行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);
return timerName;
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
dispatch_semaphore_signal(semaphore_);
}
@end
上面是以block的方式進(jìn)行封裝规求,當(dāng)然了筐付,也可以使用target的方式,相對(duì)比較簡(jiǎn)單阻肿,具體如下:
#import <Foundation/Foundation.h>
@interface ZCGCDTimer : NSObject
/**
Block方式的定時(shí)器
@param task 任務(wù)(這里使用block)
@param start 開始時(shí)間
@param interval 間隔
@param repeats 時(shí)候否重復(fù)調(diào)用
@param async 是否子線程
@return 定時(shí)器標(biāo)識(shí)(最終取消定時(shí)器是需要根據(jù)此標(biāo)識(shí)取消的)
*/
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async;
/**
Target方式的定時(shí)器
@param target 目標(biāo)對(duì)象(這里使用方法)
@param selector 調(diào)用方法
@param start 開始時(shí)間
@param interval 間隔
@param repeats 是否重復(fù)調(diào)用
@param async 是否子線程
@return 定時(shí)其標(biāo)識(shí)(最終取消定時(shí)器是需要根據(jù)此標(biāo)識(shí)取消的)
*/
+ (NSString*)timerTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
/**
取消定時(shí)器
@param timerName 定時(shí)器標(biāo)識(shí)
*/
+(void)canelTimer:(NSString*) timerName;
@end
#import "ZCGCDTimer.h"
@implementation ZCGCDTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
/**
load 與 initialize區(qū)別瓦戚,這里選用initialize
*/
+(void)initialize{
//GCD一次性函數(shù)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString*)timerTask:(void(^)(void))task
start:(NSTimeInterval) start
interval:(NSTimeInterval) interval
repeats:(BOOL) repeats
async:(BOOL)async{
/**
對(duì)參數(shù)做一些限制
1.如果task不存在,那就沒有執(zhí)行的必要(!task)
2.開始時(shí)間必須大于當(dāng)前時(shí)間
3.當(dāng)需要重復(fù)執(zhí)行時(shí)冕茅,重復(fù)間隔時(shí)間必須 >0
以上條件必須滿足,定時(shí)器才算是比較合理蛹找,否則沒必要執(zhí)行
*/
if (!task || start < 0 || (interval <= 0 && repeats)) {
return nil;
}
//if (!task || start < 0 || (interval <= 0 && repeats)) return nil; (上面的代碼有人可能會(huì)寫成這樣姨伤,都一樣,這是if的語法庸疾,里面只有一行時(shí)候可以省略{}乍楚,其他的沒區(qū)別)
/**
隊(duì)列
asyc:YES 全局隊(duì)列 dispatch_get_global_queue(0, 0) 可以簡(jiǎn)單理解為其他線程(非主線程)
asyc:NO 主隊(duì)列 dispatch_get_main_queue() 可以理解為主線程
*/
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
/**
創(chuàng)建定時(shí)器 dispatch_source_t timer
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定時(shí)器的唯一標(biāo)識(shí)
NSString *timerName = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[timerName] = timer;
dispatch_semaphore_signal(semaphore_);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
//定時(shí)任務(wù)
task();
//如果不需要重復(fù),執(zhí)行一次即可
if (!repeats) {
[self canelTimer:timerName];
}
});
//啟動(dòng)定時(shí)器
dispatch_resume(timer);
return timerName;
}
+ (NSString*)timerTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async{
if (!target || !selector) return nil;
return [self timerTask:^{
if ([target respondsToSelector:selector]) {
//(這是消除警告的處理)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
+(void)canelTimer:(NSString*) timerName{
if (timerName.length == 0) {
return;
}
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[timerName];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:timerName];
}
dispatch_semaphore_signal(semaphore_);
}
@end
block 樣式
- (void)viewDidLoad {
[super viewDidLoad];
//這里async傳值為YES届慈,即在子線程
NSString* jcdTimer = [JCGCDTimer timerTask:^{
NSLog(@"%@", [NSThread currentThread]);
} start:1 interval:2 repeats:YES async:YES];
self.jcTimer = jcdTimer;
}
-(void)dealloc{
NSLog(@"%s", __func__);
[JCGCDTimer canelTimer:self.jcTimer];
}
target樣式
- (void)viewDidLoad {
[super viewDidLoad];
//這里async傳值為NO徒溪,即在主線程
NSString* jcdTimer = [JCGCDTimer timerTask:self selector:@selector(jcTimerAction) start:1 interval:2 repeats:YES async:NO];
self.jcTimer = jcdTimer;
}
-(void)jcTimerAction{
NSLog(@"%@", [NSThread currentThread]);
}
-(void)dealloc{
NSLog(@"%s", __func__);
[JCGCDTimer canelTimer:self.jcTimer];
}
想了解更多iOS學(xué)習(xí)知識(shí)請(qǐng)聯(lián)系:QQ(814299221)