本文我主要是學(xué)習(xí)如下文章做的記錄倔撞,同時還有其他優(yōu)秀的文章沒有粘貼出來讲仰,在這里均表示感謝。
iOS RunLoop入門小結(jié)
RunLoop入門 看我就夠了
一误窖、簡介
1叮盘、作用
- 保持程序運(yùn)行
- 處理app的各種事件(比如觸摸秩贰,定時器等等)
- 節(jié)省CPU資源霹俺,提高性能。
2毒费、與線程的關(guān)系
- RunLoop是寄生于線程的消息循環(huán)機(jī)制丙唧,它能保證線程存活,而不是線性執(zhí)行完任務(wù)就消亡觅玻。
- RunLoop與線程是一一對應(yīng)的想际,每個線程只有唯一與之對應(yīng)的一個RunLoop。RunLoop在第一次獲取時創(chuàng)建溪厘,在線程結(jié)束時銷毀胡本。(這就相當(dāng)于 線程是一個類,RunLoop是類里的實(shí)例變量畸悬,這樣便于理解)侧甫。
- 子線程默認(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)
- Mode代表的是RunLoop的運(yùn)行模式。
- 一個 RunLoop 包含若干個 Mode玩祟,每個 Mode 又包含若干個 Source/Timer/Observer腹缩。
- 每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode空扎,這個Mode被稱作 CurrentMode藏鹊。
- 如果需要切換 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é)果:
分析:
子線程任務(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é)果:
分析:
并沒有被釋放芳撒,好像成功持有了子線程。
我們在[_thread start]后面再添加上一句[_thread start]再運(yùn)行試試看結(jié)果咨跌。
分析:崩潰了
因?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)行引用己儒,子線程也沒有被銷毀。
分析:
因?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é)果:子線程還是被銷毀了
分析:
因?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é)果:銷毀了
分析:
雖然添加了事件源纠修,但是是給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)的方法
斷點(diǎn)2:performSelector也是source0依然可以從堆棧得到印證。放過斷點(diǎn)1后調(diào)用了performSelector移宅,然后subThread的RunLoop開始響應(yīng)source0事件源,然后去調(diào)用對應(yīng)的方法椿疗,所以來到了斷點(diǎn)2
結(jié)果:self.myThread這個子線程的任務(wù)被執(zhí)行了漏峰,但是行完后,這個子線程被銷毀了届榄。
分析:
銷毀是因?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源的贤壁。
結(jié)果:銷毀是因?yàn)槿绻荘erfromSelector事件,RunLoop會退出返回埠忘。
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é)果:
分析:
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é)果:
實(shí)現(xiàn)了當(dāng)滑動滾動視圖時觸發(fā)timer當(dāng)結(jié)束滑動時停止的效果乾忱。
同時解決了子線程N(yùn)SRunLoopCommonModes中沒有UITrackingRunLoopMode造成的滑動視圖滑動時不能在子線程觸發(fā)timer方法的問題讥珍。