iOS开发—RunLoop详解

随着oc语言不断迭代,苹果的API也是逐步完善,RunLoop在实际开发中应用的越来越少。但是在面试中,假如面试官问你RunLoop的相关知识了解,那就相当于面试官在问你从事iOS开发工作的真实年限问题。那么下面我们就详细了解一下RunLoop的相关知识。

RunLoop是一种运行循环机制,其实就是死循环。它有两个作用:1.保证程序不退出 2.负责监听事件。我们可以理解为Runloop在不断的循环中,一直在询问线程队列中是否有待办任务。如果有任务就去处理,没有任务就休息。

Runloop模式:在官方文档介绍中,Runloop有五种应用模式,但是实际开发中,提供给我们开发者使用的就是常见的三种。

Runloop由三种元素组成:Source(事件源) Timer(定时器) Observer(观察),Runloop底层调用的是CoreFoundation框架下的CFRunloop,有兴趣的可以研究下。
Source(事件源):按照函数调用栈Source的分类
Source0:非Source1就是Source0
Source1:系统内核事件

Source

- (void)viewDidLoad {
    [super viewDidLoad];
    // GCD
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

    dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0);

    //设置回调
    dispatch_source_set_event_handler(_timer, ^{
        NSLog(@"@-----%@",[NSThread currentThread]);
    });
    //启动定时器 timer本质上也是一个source
    dispatch_resume(_timer);
}

Observer

- (void)viewDidLoad {
    [super viewDidLoad];
    //获取RunLoop
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
    //runloop是不是指针?是指针
    //需要释放嘛?不需要
    //创建观察者
    CFRunLoopObserverRef runloopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, NULL);

    //添加观察者
    CFRunLoopAddObserver(runloop, runloopObserver, kCFRunLoopDefaultMode);

    CFRelease(runloopObserver);
//    free(runloopObserver);
//    free和CFRelease(CoreFoundation下的CFRelease)的区别?
    /*
     CFRunLoopObserverCreate开辟堆空间,是个结构体。这个结构体内部有可能仍然有一个指针指向另一个堆空间。如果直接free,其内部的指针指向的堆空间就会内存泄露!
     CFRelease作为CoreFoundation提供给开发者的方法,它会处理上面的问题。
     */
}

RunLoop

- (void)viewDidLoad {
    [super viewDidLoad];
    NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    //currentRunLoop:获取当前线程的Runloop  mainRunLoop:主线程的Runloop
    /*
     1.默认模式:NSDefaultRunLoopMode
     在默认模式下,RunLoop会一直询问当前队列中是否存在待解决任务,有任务就去执行。但是当主线程中用户触发UI事件,应用此模式的RunLoop就会停止监听,从而不会触发回调事件
     */
    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
    /*
     2.UI模式:UITrackingRunLoopMode
     在UI模式下,UI模式是优先级最高的,当用户触发UI事件,会即刻触发RunLoop。但是只能被UI事件所唤醒
     */
    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
    /*
     3.占位模式:NSRunLoopCommonModes
     占位模式下,上面两种模式都会被触发
     */
    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
-(void)timerMethod{
    NSLog(@"%@--",[NSThread currentThread]);
}

RunLoop与多线程

如果RunLoop的timer回调里面有耗时操作,设置到主线程的RunLoop会使界面造成卡顿感,所以我们要把RunLoop放在子线程中。

- (void)viewDidLoad {
    [super viewDidLoad];

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

        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        NSLog(@"来了");
    }];
    [thread start];
}
-(void)timerMethod{
    NSLog(@"come here");
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

那么问题来了,假如我们使用上面的方式,那么log输出应该是怎样的呢?

2018-04-25 11:17:59.551463+0800 多线程[3867:89870] [MC] Lazy loading NSBundle MobileCoreServices.framework
2018-04-25 11:17:59.568394+0800 多线程[3867:89870] [MC] Loaded MobileCoreServices.framework
2018-04-25 11:17:59.665077+0800 多线程[3867:89994] 来了

没错,只是输出了“来了”,timer的timerMethod回调方法并没有被触发,原因是我们创建的thread子线程是个局部变量,在viewDidLoad方法执行完毕后就被释放了。

我们可以通过这样的方式进行验证。创建一个继承自NSThread的WTThread子类,在WTThread的dealloc方法中,设置log输出一下。

#import "WTThread.h"
@implementation WTThread

-(void)dealloc{
    NSLog(@"线程走了");
}
@end

//修改viewDidLoad方法
- (void)viewDidLoad {
    [super viewDidLoad];

    NSThread * thread =[[WTThread alloc]initWithBlock:^{

        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        NSLog(@"来了");
    }];
    [thread start];
}
-(void)timerMethod{
    NSLog(@"come here");
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

log输出

2018-04-25 11:17:59.665077+0800 多线程[3867:89994] 来了
2018-04-25 11:17:59.665709+0800 多线程[3867:89994] 线程走了

我们会很明显的看到WTThread的dealloc方法执行了。我们可能会猜测会不会是我们在创建thread子线程的时候创建的是局部变量,才导致的这个问题呢?我们继续验证一下。

@property(nonatomic,strong)WTThread* thread;

- (void)viewDidLoad {
    [super viewDidLoad];
    _thread =[[WTThread alloc]initWithBlock:^{
        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        NSLog(@"来了");
    }];

    [_thread start];
}

-(void)timerMethod{
    NSLog(@"come here");
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

那我们这样解决的话,可以触发timerMethod回调方法嘛?答案是依旧不会。

2018-04-25 11:30:00.750498+0800 多线程[4116:97855] [MC] Lazy loading NSBundle MobileCoreServices.framework
2018-04-25 11:30:00.779425+0800 多线程[4116:97855] [MC] Loaded MobileCoreServices.framework
2018-04-25 11:30:00.830370+0800 多线程[4116:98055] 来了

我们就会明白,我们创建的WTThread是走了的,它并没有被释放(因为没走dealloc方法),但是timerMethod回调方法为什么依旧不执行呢?

重点:我们使用strong修饰创建的thread(@property(nonatomic,strong)WTThread* thread;)它实际是属于OC对象,这个OC对象和我们所说的线程是有区别的。我们创建的WTThread* thread它代表着一条线程,但是!这不代表着这个对象就是线程本身!

线程的生命:只会由CPU决定!只和线程的任务有关系,任务没有了,线程就走了。任务如果在,线程就依旧会在!我们接下来继续验证,回到最初的代码。假如我们给创建的子线程一个永远执行不完的任务,那么这条线程就会永远被保存!

- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread * thread =[[WTThread alloc]initWithBlock:^{  
        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        NSLog(@"来了");
        while (true) {
            //啥都不干
        }
    }];
    [thread start];
}

-(void)timerMethod{
    NSLog(@"come here");
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

我们这样操作,NSThread * thread这个OC对象不会被释放,因为它代表的线程没有被释放!我们保住了thread子线程,但是!timerMethod回调依旧没有被触发?原因是啥?原因就是([[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];)timer对象被添加进RunLoop当中去了,但是我们写的while循环和RunLoop没有关系啊!那我们该咋办呢?

重点:线程中的RunLoop是不会自己主动跑起来的,它需要我们让它跑起来!但是我们在想在主线程中,RunLoop为什么会自动跑呢?因为在主线程的main函数当中,UIApplication帮我们开启了RunLoop。

NSThread * thread =[[WTThread alloc]initWithBlock:^{

        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];//RunLoop运行起来(死循环,本质上就是do while 让线程保活,只不过跟我们自己写的while (true)相比,它里面做了很多事:当前队列中有没有任务需要处理,要不要处理)
        NSLog(@"来了");
        while (true) {
            //啥都不干
        }
    }];
    [thread start];

这样,我们就可以触发timerMethod回调。那我们继续想,如何取消这个RunLoop呢?

假如我们直接 [[NSRunLoop currentRunLoop] run];那麽我们很难将RunLoop取消掉,我们可以这样做

#import "ViewController.h"
#import "WTThread.h"

@interface ViewController ()
@property(nonatomic,assign)BOOL isFinished;
//@property(nonatomic,strong)WTThread* thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _isFinished=NO;
    NSThread * thread =[[WTThread alloc]initWithBlock:^{
        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
//        [[NSRunLoop currentRunLoop] run];//死循环
        NSLog(@"来了");
        while (!_isFinished) {
            [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.00001]];//每隔0.00001秒执行一次RunLoop
        }
    }];

    [thread start];
}
//触摸开始后停止RunLoop(实际使用时结合业务逻辑使RunLoop停止)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    _isFinished = YES;
}
-(void)timerMethod{
    NSLog(@"come here");
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

@end

或者这样

#import "ViewController.h"
#import "WTThread.h"

@interface ViewController ()
@property(nonatomic,assign)BOOL isFinished;
//@property(nonatomic,strong)WTThread* thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _isFinished=NO;
    NSThread * thread =[[WTThread alloc]initWithBlock:^{
        NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];//死循环
        NSLog(@"来了");


    [thread start];
}
//触摸开始后停止RunLoop(实际使用时结合业务逻辑使RunLoop停止)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    _isFinished = YES;
}
-(void)timerMethod{
    NSLog(@"come here");
    if(_isFinished){
        [NSThread exit];
    }
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"%@--",[NSThread currentThread]);
}

@end

拓展:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [NSThread exit];
}

touchesBegan这个方法所在的线程是哪个线程呢?当然是主线程,那么在主线程中执行exit,程序会如何运行呢?答案是程序正常运行,但是用户就操作不了UI了。这就涉及到主线程和子线程的区别。
主线程和子线程的区别:
主线程其实就是程序的一个子线程
面试题:UIKit是否是线程安全的?
答案:不是线程安全的。如果出现了多条线程访问,就会出现资源抢夺。因为UIKit没有加锁
所以苹果和开发者就约定用主线程访问UI,主线程就是更新UI的作用。所以我们也叫主线程为UI线程。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值