NSRunLoop探究

来源:互联网 发布:查看linux磁盘读写速率 编辑:程序博客网 时间:2024/06/07 09:57

经常听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,即栈(内存)溢出——–
函数执行开始时push

函数执行完毕时pop

如上两图所示,每一个函数的调用都会分配一块栈内存(汇编代码中就是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:timer forMode:NSDefaultRunLoopMode];//        while (true) {//死循环//            //取出runloop中的event//            //        }//        [[NSRunLoop currentRunLoop] run];//让runloop来死循环        while (!_isFinish) {            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.00001]];        }        NSLog(@"22");    }];    [thread start];}- (void)timerMrthod {    NSLog(@"来了!!");    if (_isFinish) {        [NSThread exit];//停止线程    }    static int a = 0;    NSLog(@"%@------%d",[NSThread currentThread],a++);}- (void)touchesBegan:(NSSet<UITouch *> *)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}
原创粉丝点击