iOS Principle:Runloop(中)

简介: iOS Principle:Runloop(中)

七. 详解RunLoop相关类及作用


通过上面的分析,我们对RunLoop内部结构有了大致的了解,接下来来详细分析RunLoop的相关类。以下为Core Foundation中关于RunLoop的5个类

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


1. CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的运行模式 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source、Timer、Observer 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode 如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出 如图所示:


image.png


注意:一种Mode中可以有多个Source(事件源,输入源,基于端口事件源例键盘触摸等) Observer(观察者,观察当前RunLoop运行状态) 和Timer(定时器事件源)。但是必须至少有一个Source或者Timer,因为如果Mode为空,RunLoop运行到空模式不会进行空转,就会立刻退出。


系统默认注册的5个Mode:

RunLoop 有五种运行模式,其中常见的有1.2两种

  1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
  4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode


Mode间的切换

我们平时在开发中一定遇到过,当我们使用NSTimer每一段时间执行一些事情时滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况,我们通过一段代码来看一下 代码中的注释也很重要,展示了我们探索的过程

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 加入到RunLoop中才可以运行
    // 1. 把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 当textFiled滑动的时候,timer失效,停止滑动时,timer恢复
    // 原因:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动了
    // 2. 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    // 3. 那个如何让timer在两个模式下都可以运行呢?
    // 3.1 在两个模式下都添加timer 是可以的,但是timer添加了两次,并不是同一个timer
    // 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,下面两种模式被打上标签
    //0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
    //2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
    // 因此也就是说如果我们使用NSRunLoopCommonModes,timer可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show {
    NSLog(@"-------");
}


由上述代码可以看出,NSTimer不管用是因为Mode的切换,因为如果我们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所以NSTimer就又管用了。 同样道理的还有ImageView的显示,我们直接来看代码,不再赘述了

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
    // performSelector默认是在default模式下运行,因此在滑动ScrollView时,图片不会加载
    // [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 ];
    // inModes: 传入Mode数组
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
}

使用GCD也可是创建计时器,而且更为精确我们来看一下代码


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //1.创建一个GCD定时器
    /*
    第一个参数:表明创建的是一个定时器
    第四个参数:队列
    */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 需要对timer进行强引用,保证其不会被释放掉,才会按时调用block块
    // 局部变量,让指针强引用
    self.timer = timer;
    //2.设置定时器的开始时间,间隔时间,精准度
    /*
    第1个参数:要给哪个定时器设置
    第2个参数:开始时间
    第3个参数:间隔时间
    第4个参数:精准度 一般为0 在允许范围内增加误差可提高程序的性能
    GCD的单位是纳秒 所以要*NSEC_PER_SEC
    */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    //3.设置定时器要执行的事情
    dispatch_source_set_event_handler(timer, ^{
    NSLog(@"---%@--",[NSThread currentThread]);
    });
    // 启动
    dispatch_resume(timer);
}


2. CFRunLoopSourceRef事件源(输入源)

Source分为两种

  • Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)
  • Source1:基于Port的  通过内核和其他线程相互发送消息(与内核相关)

触摸事件及PerformSelectors会触发Source0事件源在前文已经验证过,这里不在赘述


3. CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

我们直接来看代码,给RunLoop添加监听者,监听其运行状态

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //创建监听者
    /*
    第一个参数 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);
}

我们来看一下输出


image.png


以上可以看出,Observer确实用来监听RunLoop的状态,包括唤醒,休息,以及处理各种事件。


八. RunLoop处理逻辑


这时我们再来分析RunLoop的处理逻辑,就会简单明了很多,现在回头看官方文档RunLoop的处理逻辑,对RunLoop的处理逻辑有新的认识。


image.png


源码解析

下面源码仅保留了主流程代码

// 共外部调用的公开的CFRunLoopRun方法,其内部会调用CFRunLoopRunSpecific
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 经过精简的 CFRunLoopRunSpecific 函数代码,其内部会调用__CFRunLoopRun函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    // 通知Observers : 进入Loop
    // __CFRunLoopDoObservers内部会调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__函数
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 核心的Loop逻辑
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知Observers : 退出Loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}
// 精简后的 __CFRunLoopRun函数,保留了主要代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        // 通知Observers:即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 处理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 如果有Sources1,就跳转到handle_msg标记处
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        // 通知Observers:即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 进入休眠,等待其他消息唤醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        // 醒来
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        // 通知Observers:已经唤醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
        if (被Timer唤醒的) {
            // 处理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (被GCD唤醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1唤醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        // 执行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        // 根据之前的执行结果,来决定怎么做,为retVal赋相应的值
        if (sourceHandledThisLoop && stopAfterHandle) {
            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)) {
            retVal = kCFRunLoopRunFinished;
        }
    } while (0 == retVal);
    return retVal;
}


上述源代码中,相应处理事件函数内部还会调用更底层的函数,内部调用才是真正处理事件的函数,通过上面bt打印全部堆栈信息也可以得到验证。

1)__CFRunLoopDoObservers 内部调用: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

2)__CFRunLoopDoBlocks 内部调用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK

3)__CFRunLoopDoSources0 内部调用:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

4)__CFRunLoopDoTimers 内部调用:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

5)GCD 调用:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE

6)__CFRunLoopDoSource1 内部调用:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION


RunLoop处理逻辑流程图

此时我们按照源码重新整理一下RunLoop处理逻辑就会很清晰


706f889cbdff4bdcb96416f6548d3222.png


目录
相关文章
|
iOS开发
ios实战-runloop实现的同步弹窗
我们知道UIAlertView使用delegate返回数据实现的,使用麻烦,之前介绍过用Block实现的例子《ios实战-使用Block的UIAlertView》 今天介绍使用runloop实现,用return返回点击的结果的方式
92 0
|
iOS开发
iOS Principle:CGAffineTransform
iOS Principle:CGAffineTransform
145 0
iOS Principle:CGAffineTransform
|
安全 Unix API
iOS Principle:CALayer(下)
iOS Principle:CALayer(下)
138 0
iOS Principle:CALayer(下)
|
iOS开发
iOS Principle:CALayer(中)
iOS Principle:CALayer(中)
121 0
iOS Principle:CALayer(中)
|
API C语言 iOS开发
iOS Principle:CALayer(上)
iOS Principle:CALayer(上)
144 0
iOS Principle:CALayer(上)
|
存储 缓存 iOS开发
iOS Principle:weak
iOS Principle:weak
120 0
iOS Principle:weak
|
存储 iOS开发
iOS Principle:Notification(下)
iOS Principle:Notification(下)
90 0
iOS Principle:Notification(下)
|
设计模式 iOS开发
iOS Principle:Notification(上)
iOS Principle:Notification(上)
101 0
iOS Principle:Notification(上)
|
Web App开发 JSON 移动开发
iOS Principle:ReactNative(下)
iOS Principle:ReactNative(下)
155 0
iOS Principle:ReactNative(下)
|
移动开发 前端开发 JavaScript
iOS Principle:ReactNative(中)
iOS Principle:ReactNative(中)
99 0
iOS Principle:ReactNative(中)