RunLoop學(xué)習(xí)筆記

本文我主要是學(xué)習(xí)如下文章做的記錄倔撞,同時還有其他優(yōu)秀的文章沒有粘貼出來讲仰,在這里均表示感謝。
iOS RunLoop入門小結(jié)
RunLoop入門 看我就夠了

一误窖、簡介

1叮盘、作用

  1. 保持程序運(yùn)行
  2. 處理app的各種事件(比如觸摸秩贰,定時器等等)
  3. 節(jié)省CPU資源霹俺,提高性能。

2毒费、與線程的關(guān)系

  1. RunLoop是寄生于線程的消息循環(huán)機(jī)制丙唧,它能保證線程存活,而不是線性執(zhí)行完任務(wù)就消亡觅玻。
  2. RunLoop與線程是一一對應(yīng)的想际,每個線程只有唯一與之對應(yīng)的一個RunLoop。RunLoop在第一次獲取時創(chuàng)建溪厘,在線程結(jié)束時銷毀胡本。(這就相當(dāng)于 線程是一個類,RunLoop是類里的實(shí)例變量畸悬,這樣便于理解)侧甫。
  3. 子線程默認(rèn)沒有RunLoop,需要我們?nèi)ブ鲃娱_啟蹋宦,但是主線程是自動開啟了RunLoop的披粟。

3、RunLoop三種啟動方式

1冷冗、- (void)run守屉;

這種啟動方式:RunLoop永久性的運(yùn)行NSDefaultRunLoopMode模式,即使用 CFRunLoopStop(runloopRef)也無法停止RunLoop的運(yùn)行蒿辙,那么只能永久運(yùn)行下去.

2拇泛、- (void)runUntilDate:(NSDate *)limitDate;

這種啟動方式:可以控制每次RunLoop的運(yùn)行時間,也是運(yùn)行在NSDefaultRunLoopMode模式下思灌。RunLoop一段時間會退出給你檢查運(yùn)行條件的機(jī)會俺叭,如果需要可以再次運(yùn)行RunLoop。注意CFRunLoopStop(runloopRef);仍然無法停止RunLoop的運(yùn)行习瑰。

while (!Stop){
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];
}
3绪颖、- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

. 有一個超時時間限制,而且可以設(shè)置運(yùn)行模式
. 在非Timer事件觸發(fā)、顯式的用CFRunLoopStop停止RunLoop柠横、到達(dá)limitDate后會退出返回
. 如果僅是Timer事件觸發(fā)并不會讓RunLoop退出返回窃款,但是如果是PerfromSelector事件或者其他Input Source事件觸發(fā)處理后,RunLoop會退出返回.

4牍氛、RunLoop正常運(yùn)行的條件:三個要同時滿足

1晨继、至少要包含一個Mode:RunLoop默認(rèn)包含DefaultMode
2、該Mode下需要有至少一個的事件源:對于NSRunLoop 可以往mode中添加兩類事件源(Timer/Source)

. NSTimer:[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
. NSPort(對應(yīng)的是source1):[runLoop addPort:[NSMachPort port] forMode:UITrackingRunLoopMode];

3搬俊、運(yùn)行在有事件源的Mode下紊扬。
注意:只有Observer的RunLoop也是無法正常運(yùn)行的。

5唉擂、獲取RunLoop對象

####Foundation
// 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop currentRunLoop]; 
// 獲得主線程的RunLoop對象
[NSRunLoop mainRunLoop]; 

####Core Foundation
// 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetCurrent(); 
// 獲得主線程的RunLoop對象 
CFRunLoopGetMain(); 

6餐屎、RunLoop結(jié)構(gòu)

image.png
  1. Mode代表的是RunLoop的運(yùn)行模式。
  2. 一個 RunLoop 包含若干個 Mode玩祟,每個 Mode 又包含若干個 Source/Timer/Observer腹缩。
  3. 每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode空扎,這個Mode被稱作 CurrentMode藏鹊。
  4. 如果需要切換 Mode,只能退出 Loop转锈,再重新指定一個 Mode 進(jìn)入盘寡。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響撮慨。
. Mode

Mode就是”行為模式“竿痰,就像我們說到上學(xué)這個行為模式,它就應(yīng)該包含起床甫煞,出門菇曲,去學(xué)校,上課抚吠,午休等等常潮。但是,如果上學(xué)這個行為模式什么都不包含楷力,那么即使我們進(jìn)行上學(xué)這個行為喊式,我們也一直睡在床上什么都不會做。
Mode的存在是為了讓RunLoop在不同的”行為模式“之下執(zhí)行不同的”動作“互不影響萧朝。
比如執(zhí)行上學(xué)這個行為模式就不能進(jìn)行娛樂這個行為模式下的游戲這個動作岔留。

1.kCFRunLoopDefaultMode(CFRunLoop)/NSDefaultRunLoopMode(NSRunLoop)
默認(rèn)模式,在RunLoop沒有指定Mode的時候检柬,默認(rèn)就跑在DefaultMode下献联。一般情況下App都是運(yùn)行在這個mode下的
2.(CFStringRef)UITrackingRunLoopMode(CFRunLoop)/UITrackingRunLoopMode(NSRunLoop)
一般作用于ScrollView滾動的時候的模式,保證滑動的時候不受其他事件影響。
3.kCFRunLoopCommonModes(CFRunLoop)/NSRunLoopCommonModes(NSRunLoop)
這個并不是某種具體的Mode里逆,而是一種模式組合进胯,在主線程中默認(rèn)包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。子線程中只包含NSDefaultRunLoopMode原押。
注意:
①在選擇RunLoop的runMode: beforeDate時不可以填這種模式否則會導(dǎo)致RunLoop運(yùn)行不成功胁镐。
②在添加事件源的時候填寫這個模式就相當(dāng)于向組合中所有包含的Mode中注冊了這個事件源。
③你也可以通過調(diào)用CFRunLoopAddCommonMode()方法將自定義Mode放到 kCFRunLoopCommonModes組合诸衔。
. Source

輸入源事件盯漂,分為source0和source1這兩種。

1.source0:諸如UIEvent(觸摸笨农,滑動等)就缆,performSelector這種需要手動觸發(fā)的操作。
2.source1:處理系統(tǒng)內(nèi)核的mach_msg事件(系統(tǒng)內(nèi)部的端口事件)磁餐。諸如喚醒RunLoop或者讓RunLoop進(jìn)入休眠節(jié)省資源等违崇。
一般來說日常開發(fā)中我們需要關(guān)注的是source0阿弃,source1只需要了解诊霹。
之所以說source0更重要是因?yàn)槿粘i_發(fā)中,我們需要對常駐線程進(jìn)行操作的事件大多都是source0渣淳,稍后的實(shí)驗(yàn)會講到脾还。
. Timer

定時源事件。通俗來講就是我們很熟悉的NSTimer入愧,其實(shí)NSTimer定時器的觸發(fā)正是基于RunLoop運(yùn)行的鄙漏,所以使用NSTimer之前必須注冊到RunLoop,但是RunLoop為了節(jié)省資源并不會在非常準(zhǔn)確的時間點(diǎn)調(diào)用定時器棺蛛,如果一個任務(wù)執(zhí)行時間較長怔蚌,那么當(dāng)錯過一個時間點(diǎn)后只能等到下一個時間點(diǎn)執(zhí)行,并不會延后執(zhí)行旁赊。

. Observer

它相當(dāng)于消息循環(huán)中的一個監(jiān)聽器桦踊,隨時通知外部當(dāng)前RunLoop的運(yùn)行狀態(tài)。NSRunLoop沒有相關(guān)方法终畅,只能通過CFRunLoop相關(guān)方法創(chuàng)建籍胯。

PS: Source/Timer/Observer 被統(tǒng)稱為 mode item,一個item可以被同時加入多個mode离福。但一個item被重復(fù)加入同一個mode時是不會有效果的杖狼。如果一個mode中一個item 都沒有(只有Observer也不行),則 RunLoop 會直接退出妖爷,不進(jìn)入循環(huán)蝶涩。

二、實(shí)驗(yàn)代碼

新建一個繼承自NSThread的子類

.h文件

#import <Foundation/Foundation.h>

@interface WGThread : NSThread

@end

.m文件
#import "WGThread.h"

@implementation WGThread

-(void)dealloc{
    NSLog(@"%@---子線程銷毀了",self.name);
}

@end

1、開啟一個子線程

- (void)nsthread01{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthreadMedthod) object:nil];
    thread.name = @"nsthread01";
    [thread start];
}

- (void)nsthreadMedthod{
    NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
}

結(jié)果:

image.png

分析:
子線程任務(wù)執(zhí)行完就自動銷毀了绿聘。

2暗挑、需要一個不被銷毀的子線程

. 上述代碼中的thread是局部變量,函數(shù)走完就銷毀了,那么如果用屬性來接受會怎么樣呢?子線程還會被銷毀嗎荡灾?
@property (nonatomic, strong) WGThread *thread;

- (void)nsthread02{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    self.thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthreadMedthod) object:nil];
    _thread.name = @"nsthread02";
    [_thread start];
}

結(jié)果:

image.png

分析:
并沒有被釋放芳撒,好像成功持有了子線程。
我們在[_thread start]后面再添加上一句[_thread start]再運(yùn)行試試看結(jié)果咨跌。

image.png

分析:崩潰了
因?yàn)閳?zhí)行完任務(wù)后,雖然Thread沒有被釋放,還處于內(nèi)存中惯殊,但是它處于死亡狀態(tài),蘋果不允許在線程死亡后再次開啟也殖。

. 讓線程一直處于有任務(wù)的狀態(tài)

我們在子線程中加入一個死循環(huán)

- (void)nsthread03{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    self.thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthreadMedthod) object:nil];
    _thread.name = @"nsthread03";
    [_thread start];
}

- (void)nsthreadMedthod{
    do {
        NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
    } while (1);
}

結(jié)果:
子線程確實(shí)不會進(jìn)入死亡狀態(tài)土思,但是子線程卻在不分時間地點(diǎn)場合的一直執(zhí)行任務(wù)。和我們想要的不一樣忆嗜。

. 使用RunLoop
- (void)nsthread03{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthread03Medthod) object:nil];
    thread.name = @"nsthread03";
    [thread start];
}

- (void)nsthread03Medthod{
    NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [runloop run];
    NSLog(@"%@---結(jié)束->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
}

結(jié)果:即使不用對線程用self進(jìn)行引用己儒,子線程也沒有被銷毀。

image.png

分析:
因?yàn)閇runLoop run];這一行的存在捆毫。
RunLoop本質(zhì)就是個Event Loop的do while循環(huán)闪湾,所以運(yùn)行到這一行以后子線程就一直在進(jìn)行“接受消息->等待->處理”的循環(huán)。所以不會運(yùn)行[runLoop run];之后的代碼绩卤。
可以看到下面代碼一直不會被執(zhí)行途样。

    NSLog(@"%@---結(jié)束->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
階段總結(jié): RunLoop是保證線程不會退出,并且能在不處理消息的時候讓線程休眠濒憋,節(jié)約資源何暇,在接收到消息的時候喚醒線程做出對應(yīng)處理的消息循環(huán)機(jī)制。它是寄生于線程的凛驮,所以提到RunLoop必然會涉及到線程裆站。
. 驗(yàn)證RunLoop運(yùn)行的條件

1、注釋掉 [runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];

- (void)nsthread03{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthread03Medthod) object:nil];
    thread.name = @"nsthread03";
    [thread start];
}

- (void)nsthread03Medthod{
    NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
   // [runloop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
    [runloop run];
    NSLog(@"%@---結(jié)束->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
}

結(jié)果:子線程還是被銷毀了

image.png

分析:
因?yàn)闆]有給Mode添加事件源(Timer/Source)辐烂;

2遏插、添加事件源,但是改變Mode

- (void)nsthread03{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthread03Medthod) object:nil];
    thread.name = @"nsthread03";
    [thread start];
}

- (void)nsthread03Medthod{
    NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:UITrackingRunLoopMode];
    [runloop run];
    NSLog(@"%@---結(jié)束->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
}

結(jié)果:銷毀了

image.png

分析:
雖然添加了事件源纠修,但是是給UITrackingRunLoopMode模式添加的
但是[runloop run]是在NSDefaultRunLoopMode下運(yùn)行RunLoop
也就是說在NSDefaultRunLoopMode還是沒有事件源胳嘲,不會成功啟用RunLoop。

3扣草、成功使用RunLoop保持線程存活了牛,讓線程在需要的時候執(zhí)行任務(wù)

. 常用在某線程執(zhí)行某任務(wù)的接口有:
//在主線程中響應(yīng)指定Selector颜屠。這兩個方法給你提供了選項(xiàng)來阻斷當(dāng)前線程(當(dāng)前線程不是執(zhí)行Selector的線程而是調(diào)用上述方法的線程)直到selector被執(zhí)行完畢。
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

//在某個子線程(NSThread對像)中響應(yīng)指定Selector鹰祸。這兩個方法同樣給你提供了選項(xiàng)來阻斷當(dāng)前線程直到Selector被執(zhí)行完畢甫窟。
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

//在當(dāng)前線程中執(zhí)行Selector,并附加了延遲選項(xiàng)蛙婴。多個排隊的Selector會按照順序一個一個的執(zhí)行粗井。
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

其實(shí),這幾個方法都是向線程中的RunLoop發(fā)送了消息街图,然后RunLoop接收到了消息就喚醒線程浇衬,去做對應(yīng)的事情。所以想要正常使用這幾個方法餐济,響應(yīng)selector的線程必須開啟了RunLoop耘擂。

. 前四個接口屬于source0的事件源,后兩種屬于Timer源
//我用weak修飾絮姆,當(dāng)子線程釋放的時候可以看到日志醉冤。
@property (nonatomic, weak) WGThread *myThread;

- (void)nsthread04{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthread04Medthod) object:nil];
    self.myThread = thread;
    _myThread.name = @"nsthread04";
    [_myThread start];
}

//開啟一個子線程 self.myThread,并不被銷毀
- (void)nsthread04Medthod{
    NSLog(@"%@---開始->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    NSLog(@"%@---結(jié)束->子線程在執(zhí)行任務(wù)",[NSThread currentThread]);
}

//給這個子線程 self.myThread發(fā)消息篙悯,讓這個子線程 self.myThread執(zhí)行任務(wù)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//斷點(diǎn)1
    [self performSelector:@selector(nsthread04Func) onThread:self.myThread withObject:nil waitUntilDone:NO];
}

//這個是子線程 self.myThread需要執(zhí)行的任務(wù)
- (void)nsthread04Func{
//斷點(diǎn)2
    NSLog(@"當(dāng)前線程:%@正在處理", [NSThread currentThread]);
}

斷點(diǎn)1:UIEvent事件屬于source0蚁阳,從這里的堆棧就可以得到印證。我們在主線程中觸發(fā)了touchesBegan辕近,然后主線程的RunLoop就開始響應(yīng)source0事件源韵吨,然后去調(diào)用對應(yīng)的方法

image.png

斷點(diǎn)2:performSelector也是source0依然可以從堆棧得到印證。放過斷點(diǎn)1后調(diào)用了performSelector移宅,然后subThread的RunLoop開始響應(yīng)source0事件源,然后去調(diào)用對應(yīng)的方法椿疗,所以來到了斷點(diǎn)2

image.png

結(jié)果:self.myThread這個子線程的任務(wù)被執(zhí)行了漏峰,但是行完后,這個子線程被銷毀了届榄。

image.png

分析:
銷毀是因?yàn)椋簉unMode:(NSString *)mode beforeDate:(NSDate *)limitDate這種啟動RunLoop的方式有一個特性,那就是這個接口在非Timer事件觸發(fā)(此處是達(dá)成了這個條件)浅乔、顯式的用CFRunLoopStop停止RunLoop或者到達(dá)limitDate后會退出。而例子當(dāng)中也沒有用while把RunLoop包圍起來铝条,所以RunLoop退出后子線程完成了任務(wù)最后退出了靖苇。

. 后兩種接口
- (void)nsthread05{
    NSLog(@"%@---開啟子線程",[NSThread currentThread]);
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(nsthread05Medthod) object:nil];
    self.myThread = thread;
    _myThread.name = @"nsthread05";
    [_myThread start];
}

- (void)nsthread05Medthod{
    NSLog(@"%@----開始執(zhí)行子線程任務(wù)",[NSThread currentThread]);
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    NSLog(@"%@----執(zhí)行子線程任務(wù)結(jié)束",[NSThread currentThread]);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//這么做主要是為了下一步在子線程執(zhí)行afterDelay方法
    [self performSelector:@selector(nsthread04Func) onThread:self.myThread withObject:nil waitUntilDone:NO];
}

- (void)nsthread04Func{
//當(dāng)前線程執(zhí)行任務(wù)afterDelayTodo
    [self performSelector:@selector(afterDelayTodo) withObject:nil afterDelay:0];
}

- (void)afterDelayTodo{
//斷點(diǎn)
    NSLog(@"當(dāng)前線程:%@正在處理", [NSThread currentThread]);
}

斷點(diǎn):當(dāng)調(diào)用 performSelecter:afterDelay: 后,其內(nèi)部會創(chuàng)建一個 Timer 并添加到當(dāng)前線程的 RunLoop 中班缰,所以這個方法是屬于Timer源的贤壁。

image.png

結(jié)果:銷毀是因?yàn)槿绻荘erfromSelector事件,RunLoop會退出返回埠忘。

image.png

4脾拆、 驗(yàn)證子線程中開啟NSRunLoopCommonModes是否包含NSDefaultRunLoopMode和UITrackingRunLoopMode

. 主線程中的NSRunLoopCommonModes

NSTimer定時器的觸發(fā)正是基于RunLoop運(yùn)行的馒索,所以使用NSTimer之前必須注冊到RunLoop。
如下代碼:

NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timerToDo) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

但是上面代碼會有一個問題名船,就是:在進(jìn)行Scrollview的滾動操作時Timer不進(jìn)行響應(yīng)绰上,滑動結(jié)束后timer又恢復(fù)正常了。
這是因?yàn)椋?br> 1.在之前講Mode的時候提到過渠驼,RunLoop每次只能運(yùn)行在一個Mode下蜈块,其意義是讓不同Mode中的item互不影響。

2.NSTimer是一個Timer源(item)迷扇,在上面哪個例子中不管是[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];還是scheduedTimerWith我們都是把Timer加到了主線程RunLoop的NSDefaultRunLoopMode中疯趟。一般情況下主線程RunLoop就運(yùn)行在NSDefaultRunLoopMode下,所以定時器正常運(yùn)行谋梭。

3.當(dāng)Scrollview開始滑動時信峻,主線程RunLoop自動切換了當(dāng)前運(yùn)行的Mode(currentMode),變成了UITrackingRunLoopMode瓮床。所以現(xiàn)在RunLoop要處理的就是UITrackingRunLoopMode中item盹舞。

4.我們的timer是添加在NSDefaultRunLoopMode中的,并沒有添加到UITrackingRunLoopMode中隘庄。即我們的timer不是UITrackingRunLoopMode中的item踢步。

5.本著不同Mode中的item互不影響的原則,RunLoop也就不會處理非當(dāng)前Mode的item,所以定時器就不會響應(yīng)丑掺。

6.當(dāng)Scrollview滑動結(jié)束获印,主線程RunLoop自動切換了當(dāng)前運(yùn)行的Mode(currentMode),變成了NSDefaultRunLoopMode街州。我們的Timer是NSDefaultRunLoopMode的item兼丰,所以RunLoop會處理它,所以又正常響應(yīng)了唆缴。

解決:
一個item可以被同時加入多個mode鳍征,所以可以讓Timer同時成為兩種Mode的item就可以了(分別添加或者直接加到commonMode中),這樣不管RunLoop處于什么Mode面徽,timer都是當(dāng)前Mode的item艳丛,都會得到處理。

@interface RootViewController ()<UITextViewDelegate>

@property (nonatomic, weak) NSThread *subThread;//子線程
@property (nonatomic, weak) NSRunLoopMode runLoopMode;//想設(shè)置的RunLoop的Mode
@property (nonatomic, assign) BOOL isNeedRunLoopStop;//控制是否需要停止RunLoop
@property (nonatomic, strong) UITextView *myTextView;//

@end

- (void)mainThread{
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timerToDo) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

-(UITextView *)myTextView{
    if (!_myTextView) {
        _myTextView = [[UITextView alloc] initWithFrame:CGRectMake(100, 100, 100, 200)];
        _myTextView.backgroundColor = [UIColor grayColor];
        _myTextView.textColor = [UIColor whiteColor];
        _myTextView.text = @"這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-";
    }
    return _myTextView;
}

結(jié)果:
當(dāng)滑動文本框時timer也在執(zhí)行趟紊,解決問題氮双。同時也說明NSRunLoopCommonModes包含了UITrackingRunLoopMode和NSDefaultRunLoopMode。

. 子線程中的NSRunLoopCommonModes

- (void)buildSubThread{
    self.isNeedRunLoopStop = NO;
    NSLog(@"%@----開辟子線程",[NSThread currentThread]);
    
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(subThreadToDo) object:nil];
    self.subThread = thread;
    thread.name = @"subThread";
    [thread start];
}

- (void)subThreadToDo{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timerToDo) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
    //runloop在UITrackingRunLoopMode下運(yùn)行
    self.runLoopMode = UITrackingRunLoopMode;
    while (!self.isNeedRunLoopStop) {
        [runLoop runMode:self.runLoopMode beforeDate:[NSDate distantFuture]];
    }
}

結(jié)果:
沒有觸發(fā)timer方法霎匈。

其他不變戴差,僅把
self.runLoopMode = UITrackingRunLoopMode;
改為
self.runLoopMode = NSDefaultRunLoopMode;

結(jié)果:

333.gif

分析:
1、timer都是添加在NSRunLoopCommonModes下的唧躲,但是當(dāng)該子線程的runloop運(yùn)行在UITrackingRunLoopMode時并不能觸發(fā)timer方法造挽,但是運(yùn)行在UITrackingRunLoopMode時可以碱璃。

結(jié)論:主線程的NSRunLoopCommonModes默認(rèn)是包含UITrackingRunLoopMode和NSDefaultRunLoopMode。而子線程的NSRunLoopCommonModes默認(rèn)是只包含NSDefaultRunLoopMode的饭入。

解決:

- (void)subThreadToDo{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timerToDo) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes];
    //其他代碼都沒變嵌器,就加了下面這一句
    CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), (CFStringRef)UITrackingRunLoopMode);
    self.runLoopMode = UITrackingRunLoopMode;
    while (!self.isNeedRunLoopStop) {
        [runLoop runMode:self.runLoopMode beforeDate:[NSDate distantFuture]];
    }
}

結(jié)果就和上圖一樣了,可以觸發(fā)timer方法谐丢。

還有一種解決辦法爽航,根據(jù)一個場景來體現(xiàn)

. 場景:在子線程里可以自由切換NSDefaultRunLoopMode和UITrackingRunLoopMode(類似于主線程)當(dāng)滑動時才會觸發(fā)timer事件,當(dāng)滑動結(jié)束停止timer事件

@interface RootViewController ()<UITextViewDelegate>

@property (nonatomic, weak) NSThread *subThread;//子線程
@property (nonatomic, weak) NSRunLoopMode runLoopMode;//想設(shè)置的RunLoop的Mode
@property (nonatomic, assign) BOOL isNeedRunLoopStop;//控制是否需要停止RunLoop
@property (nonatomic, strong) UITextView *myTextView;//

@end

@implementation RootViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = @"RootViewController";
    self.view.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:self.myTextView];
    [self buildSubThread];
}

- (void)buildSubThread{
    self.myTextView.delegate = self;
    self.isNeedRunLoopStop = NO;
    NSLog(@"%@----開辟子線程",[NSThread currentThread]);
    
    WGThread *thread = [[WGThread alloc] initWithTarget:self selector:@selector(subThreadToDo) object:nil];
    self.subThread = thread;
    thread.name = @"subThread";
    [thread start];
}

- (void)subThreadToDo{
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timerToDo) userInfo:nil repeats:YES];
    [runLoop addTimer:timer forMode:UITrackingRunLoopMode];
    //這里已經(jīng)注釋下面代碼了
    //CFRunLoopAddCommonMode(CFRunLoopGetCurrent(), (CFStringRef)UITrackingRunLoopMode);
    self.runLoopMode = NSDefaultRunLoopMode;
    while (!self.isNeedRunLoopStop) {
        [runLoop runMode:self.runLoopMode beforeDate:[NSDate distantFuture]];
    }
}

- (void)timerToDo{
    NSLog(@"NSTimer方法執(zhí)行--->當(dāng)前Model為:%@",[[NSRunLoop currentRunLoop] currentMode]);
}

//當(dāng)滑動視圖滑動時runloop的mode切換成UITrackingRunLoopMode
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (self.runLoopMode != UITrackingRunLoopMode) {
        [self performSelector:@selector(changeRunLoopMode:) onThread:self.subThread withObject:UITrackingRunLoopMode waitUntilDone:NO];
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (!decelerate) {
        if (self.runLoopMode != NSDefaultRunLoopMode) {
            [self performSelector:@selector(changeRunLoopMode:) onThread:self.subThread withObject:NSDefaultRunLoopMode waitUntilDone:NO modes:@[UITrackingRunLoopMode]];
        }
    }
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    if (self.runLoopMode != NSDefaultRunLoopMode) {
        [self performSelector:@selector(changeRunLoopMode:) onThread:self.subThread withObject:NSDefaultRunLoopMode waitUntilDone:NO modes:@[UITrackingRunLoopMode]];
    }
}

- (void)changeRunLoopMode:(NSRunLoopMode)mode{
    NSLog(@"當(dāng)前線程:%@ RunLoop即將將Mode改變成:%@\n", [NSThread currentThread], mode);
    self.runLoopMode = mode;
}
-(UITextView *)myTextView{
    if (!_myTextView) {
        _myTextView = [[UITextView alloc] initWithFrame:CGRectMake(100, 100, 100, 200)];
        _myTextView.backgroundColor = [UIColor grayColor];
        _myTextView.textColor = [UIColor whiteColor];
        _myTextView.text = @"這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-這是文本-";
    }
    return _myTextView;
}

結(jié)果:

333.gif

實(shí)現(xiàn)了當(dāng)滑動滾動視圖時觸發(fā)timer當(dāng)結(jié)束滑動時停止的效果乾忱。
同時解決了子線程N(yùn)SRunLoopCommonModes中沒有UITrackingRunLoopMode造成的滑動視圖滑動時不能在子線程觸發(fā)timer方法的問題讥珍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窄瘟,隨后出現(xiàn)的幾起案子衷佃,更是在濱河造成了極大的恐慌,老刑警劉巖蹄葱,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氏义,死亡現(xiàn)場離奇詭異,居然都是意外死亡图云,警方通過查閱死者的電腦和手機(jī)惯悠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竣况,“玉大人克婶,你說我怎么就攤上這事〉と” “怎么了情萤?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嘀掸。 經(jīng)常有香客問我紫岩,道長,這世上最難降的妖魔是什么睬塌? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮歇万,結(jié)果婚禮上揩晴,老公的妹妹穿的比我還像新娘。我一直安慰自己贪磺,他們只是感情好硫兰,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寒锚,像睡著了一般劫映。 火紅的嫁衣襯著肌膚如雪违孝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天泳赋,我揣著相機(jī)與錄音雌桑,去河邊找鬼。 笑死祖今,一個胖子當(dāng)著我的面吹牛校坑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播千诬,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼耍目,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了徐绑?” 一聲冷哼從身側(cè)響起邪驮,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎傲茄,沒想到半個月后毅访,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡烫幕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年俺抽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片较曼。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡磷斧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捷犹,到底是詐尸還是另有隱情弛饭,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布萍歉,位于F島的核電站侣颂,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枪孩。R本人自食惡果不足惜憔晒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔑舞。 院中可真熱鬧拒担,春花似錦、人聲如沸攻询。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钧栖。三九已至低零,卻和暖如春婆翔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背掏婶。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工啃奴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人气堕。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓纺腊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茎芭。 傳聞我的和親對象是個殘疾皇子揖膜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

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