Runloop应用详解

前言

Runloop是ios应用于线程中的一种循环机制。系统本身没有创建runloop的API,不过可以通过currentRunLoop获取当前RunLoop。主线程本身就存在一个runloop,而且是运行转态,子线程的runLoop需要手动开启,否知无法监听到输入源与定时源。子线程RunLoop随着所在子线程的事件源结束而关闭,随着所在子线程的结束而释放。

获取/创建RunLoop对象    

[NSRunLoop currentRunLoop];//当前线程的runLoop

[NSRunLoop mainRunLoop];//主线程runLoop

CFRunLoopGetCurrent();

CFRunLoopGetMain();

[NSRunLoop currentRunLoop].getCFRunLoop; //NSRunLoop转CFRunLoopRef

 NSRunLoop对象不是线程安全,如果在不同线程使用同一个runLoop对象,可以用CFRunLoopRef,保证线程安全。

添加定时器及输入源 

[[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSRunLoopMode)#>]

[[NSRunLoop currentRunLoop] addPort:<#(nonnull NSPort *)#> forMode:<#(nonnull NSRunLoopMode)#>]

CFRunLoopAddTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFRunLoopMode mode#>)

CFRunLoopAddSource(<#CFRunLoopRef rl#>, <#CFRunLoopSourceRef source#>, <#CFRunLoopMode mode#>)

启动RunLoop

[[NSRunLoop currentRunLoop] run]; //无条件且以默认的NSDefaultRunLoopMode启动

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate new]]; //指定过期时间且以默认的NSDefaultRunLoopMode启动

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate new]];//指定过期时间,指定启动方式

CFRunLoopRun(); //子线程的runLoop需要启动

CFRunLoopRunInMode(<#CFRunLoopMode mode#>, <#CFTimeInterval seconds#>, <#Boolean returnAfterSourceHandled#>)

退出RunLoop

/* 给RunLoop设置超时时间 */
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate new]]; //指定过期时间且以默认的NSDefaultRunLoopMode启动

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate new]];//指定过期时间,指定启动方式

/* 通知RunLoop停止  */
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop); 

子线程的RunLoop会随着事件源的结束而exit,所以一般不会主动去停止Runloop。

主线程场景    

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fireDemo) userInfo:nil repeats:YES];

可直接使用timer,默认为NSDefaultRunLoopMode。

[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

UIScrollView滑动时RunLoop切到了UITrackingRunLoopMode模式,默认为NSDefaultRunLoopMode的Timer不会调用,设置为NSRunLoopCommonModes就可以恢复正常。

子线程场景

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerCount) userInfo:nil repeats:NO];
       
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //添加timer,使用默认runloop模式
     
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    //timer结束后,触发breakPoint
});

子线程开启RunLoop的代码,runLoop启动时,runMode之后的代码不会执行,直到RunLoop随着Timer结束而exit后,才执行runMode之后的代码;timer的repeats为YES时,不会执行runMode之后的代码;子线程RunLoop没有UITrackingRunLoopMode模式。

//开启子线程runloop时必须添加相应的timer或port,否知运行无效
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; 
[[NSRunLoop currentRunLoop] run]; 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerCount) userInfo:nil repeats:NO];
       
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
     
    [[NSRunLoop currentRunLoop] run];
    //timer结束后,没有触发breakPoint
});

一旦调用[currentRunLoop run]这个方法开启子线程的运行循环就不能停止,阻塞run之后代码执行,子线程也不会被释放。

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];

//do something

  在run之后会阻塞代码,等待runLoop结束后才会执行。

创建常驻子线程

+ (NSThread *)networkThread {
    static dispatch_once_t onceToken;
    static NSThread *_networkThread;
    dispatch_once(&onceToken, ^{
        _networkThread = [[NSThread alloc] initWithTarget:[[self class] sharedInstance] selector:@selector(_runLoopThread) object:nil];
        [_networkThread setName:@"com.network.request"];
        [_networkThread setQualityOfService:[[NSThread mainThread] qualityOfService]];
        [_networkThread start];
    });

    return _networkThread;
}

- (void)_runLoopThread {
    // 直接使用run比较暴力,无法手动销毁线程
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run]; 

    /* 另一种形式
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    while (!_stopRunning) {
        @autoreleasepool {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }
    */
}

阻塞代码但不阻塞线程

 __block BOOL isContinue = NO;

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    isContinue = YES;
 });

 while (isContinue == NO) {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
 }
//isContinue为YES后执行后续代码

实际项目中经常在主线程用该方法实现阻塞代码但不阻塞线程操作,例如:等某个异步操作完成后再调用后续的代码块。

猜你喜欢

转载自blog.csdn.net/z119901214/article/details/82260795