iOS內(nèi)存管理(1)-CADisplayLink后控、NSTimer和GCD定時(shí)器

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的代碼作分析:

循環(huán)引用.png

在這個(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ì)形成viewControllerNSTimer對(duì)象的循環(huán)引用環(huán)荡碾,如果循環(huán)引用環(huán)沒有被打破,那么viewController在彈棧的時(shí)候就不會(huì)被釋放局装,也就不會(huì)走dealloc方法坛吁,也就不會(huì)將NSTimer對(duì)象invalidate,這個(gè)時(shí)候viewControllerNSTimer對(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)方式如下:

解決循環(huán)引用問題.png
@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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市金顿,隨后出現(xiàn)的幾起案子臊泌,更是在濱河造成了極大的恐慌,老刑警劉巖揍拆,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渠概,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嫂拴,警方通過查閱死者的電腦和手機(jī)播揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筒狠,“玉大人猪狈,你說我怎么就攤上這事”缒眨” “怎么了雇庙?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵谓形,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我状共,道長(zhǎng)套耕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任峡继,我火速辦了婚禮冯袍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碾牌。我一直安慰自己康愤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布舶吗。 她就那樣靜靜地躺著征冷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誓琼。 梳的紋絲不亂的頭發(fā)上检激,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音腹侣,去河邊找鬼叔收。 笑死,一個(gè)胖子當(dāng)著我的面吹牛傲隶,可吹牛的內(nèi)容都是我干的饺律。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼跺株,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼复濒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乒省,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤巧颈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后袖扛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洛二,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年攻锰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晾嘶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娶吞,死狀恐怖垒迂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妒蛇,我是刑警寧澤机断,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布楷拳,位于F島的核電站,受9級(jí)特大地震影響吏奸,放射性物質(zhì)發(fā)生泄漏欢揖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一奋蔚、第九天 我趴在偏房一處隱蔽的房頂上張望她混。 院中可真熱鬧,春花似錦泊碑、人聲如沸坤按。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)臭脓。三九已至,卻和暖如春腹忽,著一層夾襖步出監(jiān)牢的瞬間来累,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工窘奏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘹锁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓蔼夜,卻偏偏與公主長(zhǎng)得像兼耀,于是被迫代替她去往敵國(guó)和親压昼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子求冷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容