iOS:NSTimer的幾種創(chuàng)建方式

在iOS開發(fā)中,經(jīng)常會用到定時器赤拒,iOS中常用的定時器有三種:NSTimer秫筏,GCD诱鞠,CADisplayLink。

NSTimer創(chuàng)建定時器

 // 創(chuàng)建定時器 方式1
 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(run) userInfo:nil repeats:YES];
// 停止定時器
[timer invalidate];

// 創(chuàng)建定時器 方式2
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 將定時器添加到runloop中这敬,否則定時器不會啟動
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

// 停止定時器
[timer invalidate];

方式1會自動將創(chuàng)建的定時器以默認(rèn)方式添加到當(dāng)前線程runloop中航夺,而無需手動添加。但是在此種模式下崔涂,當(dāng)滾動屏幕時runloop會進(jìn)入另外一種模式阳掐,定時器會暫停,為了解決這種問題冷蚂,可以像方式2那樣把定時器添加到NSRunLoopCommonModes模式下缭保。

方式1和方式2在設(shè)置后都會在間隔設(shè)定的時間(本例中設(shè)置為1s)后執(zhí)行test方法,如果需要立即執(zhí)行可以使用下面的代碼蝙茶。

[timer fire];

注意:NSTimer創(chuàng)建的定時器艺骂,使用時會造成循環(huán)引用(target對self做了強(qiáng)引用,self又對timer進(jìn)行了強(qiáng)引用)隆夯,從而導(dǎo)致內(nèi)存泄漏钳恕。

解決NSTimer循環(huán)應(yīng)用的方法:

1.合適的時機(jī)關(guān)閉定時器

#pragma mark - 第一種方法:合適的時機(jī)關(guān)閉定時器
- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (!parent) {
        [_timer invalidate];
        _timer = nil;
    }
}

2.更改target,并給target通過Runtime添加方法

// 添加屬性
@property (nonatomic, strong) id target;

// 第二種方法
_target = [NSObject new];
class_addMethod([_target class], @selector(run), class_getMethodImplementation([self class], @selector(run)), "v@:");
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_target selector:@selector(fire) userInfo:nil repeats:YES];

- (void)dealloc {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
}

- (void)run
{
    NSLog(@"Hello");
}

3.通過使用NSProxy解決循環(huán)引用
新建一個FKProxy繼承自NSProxy蹄衷,添加屬性target忧额,實現(xiàn)方法

@property (nonatomic, weak) id target;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.target];
}

把timer的target改為FKProxy類型的對象

@property (nonatomic, strong) FKProxy *fkProxy;

_fkProxy = [FKProxy alloc]; //FKProxy只有alloc方法,沒有init方法
_fkProxy.target = self;
_timer3 = [NSTimer scheduledTimerWithTimeInterval:1.0 target:_fkProxy selector:@selector(run) userInfo:nil repeats:YES];

4.添加一個NSTimer的分類愧口,使用block解決循環(huán)應(yīng)用
分類實現(xiàn)方法

+ (NSTimer *)fk_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block {
    void (^inBlock)(void) = [block copy];
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(fk_blockHandle:) userInfo:inBlock repeats:repeats];
}

+ (void)fk_blockHandle:(NSTimer *)timer
{
    if (timer.userInfo) {
        void(^block)(void) = (void (^)(void))timer.userInfo;
        block();
    }
}

使用分類方法創(chuàng)建定時器

__weak typeof (self) weakSelf = self;
_timer4 = [NSTimer fk_scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    __strong typeof (weakSelf) strongSelf = weakSelf;
    [strongSelf run];
}];

GCD創(chuàng)建定時器

@property (nonatomic ,strong)dispatch_source_t timer; //  注意:此處應(yīng)該使用強(qiáng)引用 strong
// GCDTimer
- (void)gcdTimerTest {
    // 1. 創(chuàng)建隊列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 2.創(chuàng)建GCD中的定時器
    /*
     第一個參數(shù):創(chuàng)建source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時器
     第二個參數(shù):0
     第三個參數(shù):0
     第四個參數(shù):隊列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    // 3.設(shè)置定時的開始時間睦番、間隔時間
    /*
     第一個參數(shù):定時器對象
     第二個參數(shù):DISPATCH_TIME_NOW 表示從現(xiàn)在開始計時
     第三個參數(shù):間隔時間 GCD里面的時間最小單位為 納秒
     第四個參數(shù):精準(zhǔn)度(表示允許的誤差,0表示絕對精準(zhǔn))
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    // 4.設(shè)置定時器回調(diào)
    dispatch_source_set_event_handler(timer, ^{
        [self run];
    });

    // 5.啟動定時器,默認(rèn)是關(guān)閉的
    dispatch_resume(timer);

    self.timer = timer;
}

此處注意一定要強(qiáng)引用定時器 耍属,否則定時器執(zhí)行到 } 后將會被釋放托嚣,無定時效果。

GCD定時器時間非常精準(zhǔn)恬涧,最小的定時時間可以達(dá)到1納秒注益,所以用在非常精確的定時場合。

CADisplayLink創(chuàng)建定時器

// 創(chuàng)建displayLink
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(run:)];
// 將創(chuàng)建的displayLink添加到runloop中溯捆,否則定時器不會執(zhí)行
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

// 停止定時器
[displayLink invalidate];
displayLink = nil;

當(dāng)把CADisplayLink對象add到runloop中后,selector就能被周期性調(diào)用厦瓢,類似于重復(fù)的NSTimer被啟動了提揍;執(zhí)行invalidate操作時,CADisplayLink對象就會從runloop中移除煮仇,selector調(diào)用也隨即停止劳跃,類似于NSTimer的invalidate方法.

調(diào)用時機(jī):
CADisplayLink是一個和屏幕刷新率同步的定時器類。CADisplayLink以特定模式注冊到runloop后浙垫,每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候刨仑,runloop就會向CADisplayLink指定的target發(fā)送一次指定的selector消息郑诺,CADisplayLink類對應(yīng)的selector就會被調(diào)用一次,所以可以使用CADisplayLink做一些和屏幕操作相關(guān)的操作杉武。

重要屬性:

  • frameInterval
    NSInteger類型的值辙诞,用來設(shè)置間隔多少幀調(diào)用一次selector方法,默認(rèn)值是1轻抱,即每幀都調(diào)用一次飞涂。

  • duration
    readOnly的CFTimeInterval值,表示兩次屏幕刷新之間的時間間隔祈搜。需要注意的是较店,該屬性在target的selector被首次調(diào)用以后才會被賦值。selector的調(diào)用間隔時間計算方式是:調(diào)用間隔時間 = duration × frameInterval容燕。

比較NSTimer梁呈,GCD,CADisplayLink

NSTimer與GCD相比較,NSTimer計時不準(zhǔn)確蘸秘,原因:

  • RunLoop循環(huán)處理的時間
  • 受RunLoop模式的影響

NSTimer與GCD是不同的

  • 都是源捧杉,一個是RunLoop的源,一個Dispatch的源
  • GCD timer不需要加入mode

總結(jié)

  • GCD timer精度高
  • GCD timer主線程執(zhí)行會RunLoop影響秘血,子線程不受影響
  • GCD timer不受模式切換的影響

下面結(jié)合NSTimer來介紹 CADisplayLink味抖,與NSTimer不同的地方有:

  • 1、原理不同
    CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時器類灰粮。 CADisplayLink以特定模式注冊到runloop后仔涩, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候,runloop就會向 CADisplayLink指定的target發(fā)送一次指定的selector消息粘舟, CADisplayLink類對應(yīng)的selector就會被調(diào)用一次熔脂。 NSTimer以指定的模式注冊到runloop后,每當(dāng)設(shè)定的周期時間到達(dá)后柑肴,runloop會向指定的target發(fā)送一次指定的selector消息霞揉。

  • 2、周期設(shè)置方式不同
    iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz晰骑,因此CADisplayLink的selector 默認(rèn)調(diào)用周期是每秒60次适秩,這個周期可以通過frameInterval屬性設(shè)置, CADisplayLink的selector每秒調(diào)用次數(shù)=60/ frameInterval硕舆。比如當(dāng) frameInterval設(shè)為2秽荞,每秒調(diào)用就變成30次。因此抚官, CADisplayLink 周期的設(shè)置方式略顯不便扬跋。
    NSTimer的selector調(diào)用周期可以在初始化時直接設(shè)定,相對就靈活的多凌节。

  • 3钦听、精確度不同
    iOS設(shè)備的屏幕刷新頻率是固定的洒试,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當(dāng)高朴上。
    NSTimer的精確度就顯得低了點垒棋,比如NSTimer的觸發(fā)時間到的時候,runloop如果在忙于別的調(diào)用余指,觸發(fā)時間就會推遲到下一個runloop周期捕犬。更有甚者,在OS X v10.9以后為了盡量避免在NSTimer觸發(fā)時間到了而去中斷當(dāng)前處理的任務(wù)酵镜,NSTimer新增了tolerance屬性碉碉,讓用戶可以設(shè)置可以容忍的觸發(fā)的時間范圍。

  • 4淮韭、使用場合
    從原理上不難看出垢粮, CADisplayLink 使用場合相對專一, 適合做界面的不停重繪靠粪,比如視頻播放的時候需要不停地獲取下一幀用于界面渲染蜡吧。
    NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用占键。

demo地址:https://github.com/finleyang/FKTimer/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昔善,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子畔乙,更是在濱河造成了極大的恐慌君仆,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲距,死亡現(xiàn)場離奇詭異返咱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)牍鞠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門咖摹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人难述,你說我怎么就攤上這事萤晴。” “怎么了龄广?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵硫眯,是天一觀的道長。 經(jīng)常有香客問我择同,道長,這世上最難降的妖魔是什么净宵? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任敲才,我火速辦了婚禮裹纳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘紧武。我一直安慰自己剃氧,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布阻星。 她就那樣靜靜地躺著朋鞍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妥箕。 梳的紋絲不亂的頭發(fā)上滥酥,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音畦幢,去河邊找鬼坎吻。 笑死,一個胖子當(dāng)著我的面吹牛宇葱,可吹牛的內(nèi)容都是我干的瘦真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼黍瞧,長吁一口氣:“原來是場噩夢啊……” “哼诸尽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起印颤,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤您机,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后膀哲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體往产,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年某宪,在試婚紗的時候發(fā)現(xiàn)自己被綠了仿村。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴喂,死狀恐怖蔼囊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衣迷,我是刑警寧澤畏鼓,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站壶谒,受9級特大地震影響云矫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汗菜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一让禀、第九天 我趴在偏房一處隱蔽的房頂上張望挑社。 院中可真熱鬧,春花似錦巡揍、人聲如沸痛阻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阱当。三九已至,卻和暖如春糜工,著一層夾襖步出監(jiān)牢的瞬間弊添,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工啤斗, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留表箭,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓钮莲,卻偏偏與公主長得像免钻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崔拥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359