NSRunLoop探究

經(jīng)常聽runloop的黑魔法,但是項(xiàng)目里不怎么用,但是該了解一下還是需要的。

從main.m說起

正常的main函數(shù)如下:

int? main(int? argc,char* argv[]) {

@autoreleasepool{

return? UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate? class]));

? ? ? ? }

}

現(xiàn)對main函數(shù)進(jìn)行一些無傷大雅的修改:

int? main(int? argc,char* argv[]) {

@autoreleasepool{

? ? ? ?NSLog(@"come here");

? ? ?//1.可能是阻塞式函數(shù)

? ? ? ? 2.可能是死循環(huán)所以下面不會執(zhí)行打?。ㄒ?yàn)樗姥h(huán)了--runloop)來了

//死循環(huán)?。∈褂胷unloop,保證該線程不退出

int? a = UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate? class]));

NSLog(@"來了");

return a;

}

}

如注釋所說下面一句打印 來了 永遠(yuǎn)都不會執(zhí)行,原因是UIApplicationMain函數(shù)在runloop中循環(huán)執(zhí)行,類似死循環(huán),所以下一句永遠(yuǎn)不會執(zhí)行。

(遞歸同樣會產(chǎn)生這種效果,但這里不是遞歸原因:

遞歸:函數(shù)調(diào)用自己!!會調(diào)用Stack Overflow,即棧(內(nèi)存)溢出--------

函數(shù)執(zhí)行開始時push
函數(shù)執(zhí)行完畢時pop

如上兩圖所示,每一個函數(shù)的調(diào)用都會分配一塊棧內(nèi)存(匯編代碼中就是pushq,在函數(shù)return前都需要將其popq掉)。-----遞歸在不斷得調(diào)用自己,不斷得pushq而沒有popq,而內(nèi)存空間又是有限的,所以會造成內(nèi)存溢出,程序崩潰;而死循環(huán)里面,函數(shù)只調(diào)用一次,不斷執(zhí)行里面內(nèi)容,只pushq一次,所以不會造成內(nèi)存溢出


RunLoop的目的:

1. 保證線程不退出(主線程死循環(huán),始終都有任務(wù))

2. 監(jiān)聽事件(觸摸、時鐘、網(wǎng)絡(luò)等)

3. 無事則休眠

了解過RunLoop的都會知道runloop內(nèi)容包含有:

1、Mode----模式。

2、Observer----觀察者

3、Source----事件源

這里先只介紹Model。有幾種模式,我們常用的就是三種默認(rèn)、UI和commonModes,分別對應(yīng)NSDefaultRunLoopMode、UITrackingRunLoopMode和NSRunLoopCommonModes。

在我上一篇文章中(NSTimer)提到,NSTimer的三種創(chuàng)建方式,在scheduledTimerWithTimeInterval方法創(chuàng)建的timer會自動添加到一個模式為默認(rèn)模式(NSDefaultRunLoopMode)的runloop中;其他兩種方式則不會自動添加進(jìn)runloop,為了timer起效所以需要手動添加:

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode

第二個參數(shù)mode就是runloop的模式。

//將timer添加到runloop監(jiān)聽

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//默認(rèn)runloop模式,拖拽等用戶交互事件時,timer暫停

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];//UIrunloop模式,在有觸摸事件時才執(zhí)行timer

/*上面兩行同時存在才能確保在觸摸和未觸摸時都執(zhí)行timer,效果等同于下面一行,添加到commonModes(包含默認(rèn)和UI模式)占位模式*/

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

RunLoop和線程的關(guān)系:

一條線程上可以創(chuàng)建一個runloop,然后并沒有顯示創(chuàng)建runloop的api接口,runloop的創(chuàng)建都是封裝在獲取RunLoop額方法和函數(shù)中的,這類似于懶加載:

[NSRunLoop currentRunLoop];

看以下代碼:

先寫一個事件方法;

- (void)timerMrthod {

NSLog(@"來了??!");

static int a =0;

NSLog(@"%@------%d",[NSThread currentThread],a++);

}


然后開啟一個線程去執(zhí)行:

- (void)viewDidLoad {

? [superviewDidLoad];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

NSLog(@"22");

}];

[thread start];

}


WQthread的dealloc方法中:

#import"WQthread.h"

@implementation WQthread

- (void)dealloc {

NSLog(@"dealloc");

}

@end

運(yùn)行結(jié)果:

2017-09-27 17:10:12.104 Runloop--01[2493:112316] 22

2017-09-27 17:10:12.105 Runloop--01[2493:112316] dealloc

并沒有進(jìn)入timer的事件方法,線程thread就銷毀了,因?yàn)樵趖imer設(shè)定的定時時間到來前對象已經(jīng)銷毀了。若要執(zhí)行,則需保證thread始終存在,可以通過在線程中死循環(huán)保證線程一直有任務(wù)的方式來實(shí)現(xiàn),比如線程代碼改為:

- (void)viewDidLoad {

[superviewDidLoad];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

[[NSRunLoop currentRunLoop]run];//讓runloop來死循環(huán)

NSLog(@"22");

}];

[thread start];

}

結(jié)果:

2017-09-27 17:18:50.569 Runloop--01[2642:117818]來了??!

2017-09-27 17:18:50.570 Runloop--01[2642:117818] {number = 3, name = (null)}------0

2017-09-27 17:18:51.567 Runloop--01[2642:117818]來了?。?/b>

2017-09-27 17:18:51.568 Runloop--01[2642:117818] {number = 3, name = (null)}------1

停止線程用 [NSThreadexit];

完整代碼:

@interface ViewController()

@property(nonatomic,strong)WQthread*thread;

//保住了OC對象NSThread,但是底層的線程掛了

@property(nonatomic,assign)BOOL isFinish;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

_isFinish=NO;

//_thread = [[WQthread alloc]initWithBlock:^{

//NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

//[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

//NSLog(@"22");

//}];

//[_thread start];

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSTimer*timer = [NSTimer? timerWithTimeInterval:1.0 target:self selector:@selector(timerMrthod) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timerfor Mode:NSDefaultRunLoopMode];

//while (true) {//死循環(huán)

////取出runloop中的event

//

//}

//[[NSRunLoop currentRunLoop] run];//讓runloop來死循環(huán)

while (!_isFinish) {

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.00001]];

}

NSLog(@"22");

}];

[threadstart];

}

- (void)timerMrthod {

NSLog(@"來了??!");

if(_isFinish) {

[NSThreadexit];//停止線程

}

static int a =0;

NSLog(@"%@------%d",[NSThreadcurrentThread],a++);

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

_isFinish=YES;

//[NSThread exit];//這里如果這樣寫僅僅是停止了主線程,單子線程還在執(zhí)行任務(wù)

}

使用一個BOOL來標(biāo)記,在點(diǎn)擊屏幕的時候結(jié)束線程。

子線程和主線程進(jìn)行通信

還是在touchBegin里面來做,直接上代碼:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {

_finish=NO;

WQthread*thread = [[WQthread alloc]initWithBlock:^{

NSLog(@"touchesBegan - %@",[NSThread currentThread]);

while(!_finish) {//while循環(huán)只要不停止,該線程就會一直有任務(wù)就不會掛掉,同時添加進(jìn)當(dāng)前線程的otherMdthod任務(wù)就永遠(yuǎn)不會輪到執(zhí)行

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//這句等于是給該線程一個任務(wù),執(zhí)行完畢,該線程即進(jìn)行下一個任務(wù),即從主線程添加進(jìn)來的otherMdthod方法

//[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//UI模式不執(zhí)行主線程添加到該線程默認(rèn)模式的otherMdthod任務(wù)。

}

}];

[thread start];

[self performSelector:@selector(otherMdthod) onThread:thread withObject:nil waitUntilDone:NO];//這一句相當(dāng)于在主線程中,添加一個方法到子線程thread中,即主線程和子線程進(jìn)行了通信;thread此時相當(dāng)于從主線程添加到thread的一個source

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、什么是runloop 字面意思是“消息循環(huán)、運(yùn)行循環(huán)”。它不是線程,但它和線程息息相關(guān)。一般來講,一個線程一次...
    WeiHing閱讀 8,305評論 11 111
  • 基本概念 進(jìn)程 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序,而且每個進(jìn)程之間是獨(dú)立的,它們都運(yùn)行在其專用且受保護(hù)的內(nèi)存...
    小楓123閱讀 1,011評論 0 1
  • ======================= 前言 RunLoop 是 iOS 和 OSX 開發(fā)中非?;A(chǔ)的一個...
    i憬銘閱讀 989評論 0 4
  • 2017-08-05iOS開發(fā) 1.啟動RunLoop 通過[NSRunLoop currentRunLoop]或...
    C9090閱讀 1,344評論 0 0
  • runtime 和 runloop 作為一個程序員進(jìn)階是必須的,也是非常重要的, 在面試過程中是經(jīng)常會被問到的, ...
    made_China閱讀 1,273評論 0 7

友情鏈接更多精彩內(nèi)容