經(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ù)的調(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
}