iOS-Developer-OCiOS - Developer - OC 进阶大全

深入理解RunLoop

2019-08-01  本文已影响9人  zwwuchn

RunLoop的概念

RunLoop是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说手势识别、UI刷新事件、定时器事件、事件响应、Selector事件、网络请求、AutoreleasePool),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。

void CFRunLoopRun(void) {   
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

从上面函数可以看出,线程执行了这个函数后,就会一直处于这个函数内部 "接收消息->处理->等待-接收消息" 的循环中,直到这个循环结束

我们新建一个项目证明程序启动是否默认开启了RunLoop

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"开始---%@",[NSThread currentThread]);
        int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"结束---%@",[NSThread currentThread]);
        return result;
    }
}
// 打印结果
// 开始---<NSThread: 0x600000079bc0>{number = 1, name = main}

运行程序,我们发现只会打印开始,并不会打印结束,这再次说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回0,保持运行状态,不让程序退出。

RunLoop对象

//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop与线程的关系

Core Foundation中关于RunLoop的5个类

FRunLoopRef  //获得当前RunLoop和主RunLoop
CFRunLoopModeRef  //运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef  //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者
RunLoop.png

内部结构

1.CFRunLoopRef的类型:
>每个CFRunLoopRef 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
>CFRunLoopRef获取Mode的接口:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...); 
CFRunLoopRef的结构:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set 存放通用模式(kCFRunLoopDefaultMode、UITrackingRunLoopMode)
    CFMutableSetRef _commonModeItems; // Set 存放放入_commonModes的timer\soure0\soure1
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set 存放CFRunLoopModeRef
    ...
};
2.CFRunLoopModeRef的类型
>1. kCFRunLoopDefaultMode
App的默认Mode,通常主线程是在这个Mode下运行
>2. UITrackingRunLoopMode:
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
>3. UIInitializationRunLoopMode:
 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
>4. GSEventReceiveRunLoopMode: 
接受系统事件的内部 Mode,通常用不到
>5. kCFRunLoopCommonModes: 
这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
CFRunLoopModeRef的结构:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set 
    CFMutableSetRef _sources1;    // Set 
    CFMutableArrayRef _observers; // Array 
    CFMutableArrayRef _timers;    // Array 
    ...
};
3.CFRunLoopSourceRef
>1. 是事件产生的地方。Source有两个版本:Source0 和 Source1。
>2. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
>3. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.

4. CFRunLoopObserverRef
>基于时间的触发器,基本上说的就是NSTimer()
>1.NSTimer会受到runloop的mode影响
>2.GCD的定时器不受runloop的mode影响
5. CFRunLoopObserverRef
>CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。
我们直接来看代码,给RunLoop添加监听者,监听其运行状态:

 // 创建监听者
     /*
     第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
     第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
     第三个参数 Boolean repeats:YES:持续监听 NO:不持续
     第四个参数 CFIndex order:优先级,一般填0即可
     第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
     */
     /*
     所有事件
     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
     kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
     kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
     kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
     kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
     kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
     kCFRunLoopAllActivities = 0x0FFFFFFFU
     };
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要处理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要处理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要休息了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop醒来了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;
 
            default:
                break;
        }
    });
 
    // 给RunLoop添加监听者
    /*
     第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
     第二个参数 CFRunLoopObserverRef observer 监听者
     第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     /*CF的内存管理(Core Foundation)
     凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了*/
    CFRelease(observer);

RunLoop运行逻辑

RunLoop运行逻辑.png
从源代码来证明
void CFRunLoopRun(void) {   
    int32_t result;
    do {
        // 用DefaultMode启动
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// Runloop的主要实现(我们删减一些不必要的代码)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {  
   
   // 1. 通知 Observers: RunLoop 即将进入 loop。
     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
     // 2. 内部函数,进入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 3. 通知 Observers: RunLoop 即将退出 loop。
     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        // 2. 通知 Observers: RunLoop 即将触发 Timer 回调
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 4. 执行被加入的block
        __CFRunLoopDoBlocks(rl, rlm);
        // 5. RunLoop 触发 Source0 (非port) 回调
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 执行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
    }
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // 6. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // goto 8
            goto handle_msg;
        }
        // 7. 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
    if (!sourceHandledThisLoop) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);

        // 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
        // • 一个基于 port 的Source 的事件。
        // • 一个 Timer 到时间了
        // • RunLoop 自身的超时时间到了
        // • 被其他什么调用者手动唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        // 8. 通知 Observers: RunLoop 的线程结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        // 收到消息,处理消息
        handle_msg:;
        if (msg_is_timer) {
            // 8.1 如果被Timer唤醒,触发Timer的回调
         (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
        } else if (livePort == dispatchPort) {
            // 8.2 如果被GCD唤醒,触发GCD的回调
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

        } else {
            // 8.3 如果被source1唤醒,触发source1回调
         __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
        // 9 执行被加入的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 10 设置返回值
        if (sourceHandledThisLoop && stopAfterHandle) {
            // 进入loop时参数说处理完事件就返回
            retVal = kCFRunLoopRunHandledSource;
            } else if (timeout_context->termTSR < mach_absolute_time()) {
                // 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
                // 被外部调用者强制停止了
                __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    // 11. 最后hi根据retVal的值,通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return retVal;
}

关于线程保活

比如开发过程有很多这样的需求,保持一个线程的生命周期,让次线程一直处理事件
#import "ViewController.h"
#import "LPThread.h"

@interface ViewController ()
@property (strong, nonatomic) LPThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    self.stopped = NO;
    self.thread = [[LPThread alloc] initWithBlock:^{
    // 往RunLoop里面添加Source\Timer\Observer
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (!self.thread) return;
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
// 子控制器创建一个按钮:点击按钮停止子线程RunLoop
- (IBAction)stop {
  if (!self.thread) return;
    // 在子线程调用stop
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为NO
    self.stopped = YES;
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

定时器

NSTimer 其实就是 CFRunLoopTimerRef,一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。下面两段代码对应不同的场景
// 系统自动创建RunLoop,mode模式是kCFRunLoopDefaultMode模式,如果滑动页面定时器就是停止
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"我是系统添加NSRunLoopMode定时器");
        }];
//-------------------------------------------------------  
// 自定义RunLoop的mode模式 NSRunLoopCommonModes,kCFRunLoopDefaultMode、UITrackingRunLoopMode都能运行
// NSRunLoopCommonModes并不是一种真的模式,只是一种标记,内部存放mode

NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"我是自定义NSRunLoopMode定时器");
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//-------------------------------------------------------

PerformSelecter

当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

关于GCD

当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。
上一篇下一篇

猜你喜欢

热点阅读