NSRunLoop探究

经常听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,即栈(内存)溢出——–
NSRunLoop探究_第1张图片

NSRunLoop探究_第2张图片

如上两图所示,每一个函数的调用都会分配一块栈内存(汇编代码中就是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 *)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

}

你可能感兴趣的:(iOS开发笔记,NSRunLoop)