经常听runloop的黑魔法,但是项目里不怎么用,但是该了解一下还是需要的。
从main.m说起
正常的main函数如下:
int main(int argc,char* argv[]) {
@autoreleasepool{
return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
}
}
现对main函数进行一些无伤大雅的修改:
int main(int argc,char* argv[]) {
@autoreleasepool{
NSLog(@"come here");
//1.可能是阻塞式函数
2.可能是死循环所以下面不会执行打印(因为死循环了--runloop)来了
//死循环!!使用runloop,保证该线程不退出
int a = UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
NSLog(@"来了");
return a;
}
}
如注释所说下面一句打印 来了 永远都不会执行,原因是UIApplicationMain函数在runloop中循环执行,类似死循环,所以下一句永远不会执行。
(递归同样会产生这种效果,但这里不是递归原因:
递归:函数调用自己!!会调用Stack Overflow,即栈(内存)溢出--------
如上两图所示,每一个函数的调用都会分配一块栈内存(汇编代码中就是pushq,在函数return前都需要将其popq掉)。-----递归在不断得调用自己,不断得pushq而没有popq,而内存空间又是有限的,所以会造成内存溢出,程序崩溃;而死循环里面,函数只调用一次,不断执行里面内容,只pushq一次,所以不会造成内存溢出
)
RunLoop的目的:
1. 保证线程不退出(主线程死循环,始终都有任务)
2. 监听事件(触摸、时钟、网络等)
3. 无事则休眠
了解过RunLoop的都会知道runloop内容包含有:
1、Mode----模式。
2、Observer----观察者
3、Source----事件源
这里先只介绍Model。有几种模式,我们常用的就是三种默认、UI和commonModes,分别对应NSDefaultRunLoopMode、UITrackingRunLoopMode和NSRunLoopCommonModes。
在我上一篇文章中(NSTimer)提到,NSTimer的三种创建方式,在scheduledTimerWithTimeInterval方法创建的timer会自动添加到一个模式为默认模式(NSDefaultRunLoopMode)的runloop中;其他两种方式则不会自动添加进runloop,为了timer起效所以需要手动添加:
- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
第二个参数mode就是runloop的模式。
//将timer添加到runloop监听
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];//默认runloop模式,拖拽等用户交互事件时,timer暂停
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];//UIrunloop模式,在有触摸事件时才执行timer
/*上面两行同时存在才能确保在触摸和未触摸时都执行timer,效果等同于下面一行,添加到commonModes(包含默认和UI模式)占位模式*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
RunLoop和线程的关系:
一条线程上可以创建一个runloop,然后并没有显示创建runloop的api接口,runloop的创建都是封装在获取RunLoop额方法和函数中的,这类似于懒加载:
[NSRunLoop currentRunLoop];
看以下代码:
先写一个事件方法;
- (void)timerMrthod {
NSLog(@"来了!!");
static int a =0;
NSLog(@"%@------%d",[NSThread currentThread],a++);
}
然后开启一个线程去执行:
- (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
运行结果:
2017-09-27 17:10:12.104 Runloop--01[2493:112316] 22
2017-09-27 17:10:12.105 Runloop--01[2493:112316] dealloc
并没有进入timer的事件方法,线程thread就销毁了,因为在timer设定的定时时间到来前对象已经销毁了。若要执行,则需保证thread始终存在,可以通过在线程中死循环保证线程一直有任务的方式来实现,比如线程代码改为:
- (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来死循环
NSLog(@"22");
}];
[thread start];
}
结果:
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]来了!!
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) {//死循环
////取出runloop中的event
//
//}
//[[NSRunLoop currentRunLoop] run];//让runloop来死循环
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];//这里如果这样写仅仅是停止了主线程,单子线程还在执行任务
}
使用一个BOOL来标记,在点击屏幕的时候结束线程。
子线程和主线程进行通信
还是在touchBegin里面来做,直接上代码:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
_finish=NO;
WQthread*thread = [[WQthread alloc]initWithBlock:^{
NSLog(@"touchesBegan - %@",[NSThread currentThread]);
while(!_finish) {//while循环只要不停止,该线程就会一直有任务就不会挂掉,同时添加进当前线程的otherMdthod任务就永远不会轮到执行
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//这句等于是给该线程一个任务,执行完毕,该线程即进行下一个任务,即从主线程添加进来的otherMdthod方法
//[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];//UI模式不执行主线程添加到该线程默认模式的otherMdthod任务。
}
}];
[thread start];
[self performSelector:@selector(otherMdthod) onThread:thread withObject:nil waitUntilDone:NO];//这一句相当于在主线程中,添加一个方法到子线程thread中,即主线程和子线程进行了通信;thread此时相当于从主线程添加到thread的一个source
}