RunLoop 初识(1.基本概念以及基本认识)

一、RunLoop 简介

1.1 RunLoop 基本概念

一个线程一次只能执行一个任务,执行完成后线程就会退出。RunLoop 机制能让线程随时处理事件但并不退出。这里说的随时是指:程序需要运行时就保持程序的持续运行,不需要的时候就进入休眠状态。

NSRunLoop 和 CFRunLoopRef 都是和RunLoop 机制相关的类。CFRunLoopRef 基于 CoreFoundation 框架内,是纯 C 函数的 API,所有这些 API 都是线程安全的。CFRunLoopRef 的代码是开源的。NSRunLoop 是基于 CFRunLoopRef ,提供了面向对象的 API,但是这些 API 不是线程安全的。

1.3 为什么 main 函数不会 return掉 ?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

上面main函数同一般函数相比,启动程序后并不会立刻 return 掉。其中UIApplicationMain函数内部默认开启了主线程的 RunLoop ,并执行了一段无限循环的代码。UIApplicationMain函数一直没有返回,所以运行程序之后会保持持续运行状态。

//无限循环代码模式
int main(int argc, char * argv[]) {        
    BOOL running = YES;
    do {
        // 执行各种任务,处理各种事件
        // ......
    } while (running);

    return 0;
}

二、RunLoop 相关接口

2.1 RunLoop 的结构

和 RunLoop 相关的主要涉及五个类:

  • CFRunLoopRef:RunLoop对象
  • CFRunLoopModeRef:运行模式
  • CFRunLoopSourceRef:输入源/事件源
  • CFRunLoopTimerRef:定时源
  • CFRunLoopObserverRef:观察者

 

从上图可以看出,RunLoop 对象中可以包含多个 Mode,每个 Mode 又包含多个个 Source、Timer、Observer。

 

2.2 RunLoop 中的 Mode

关于Mode首先要知道一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。切换 Mode,需要重新指定一个 Mode 。主要是为了分隔开不同的 Source、Timer、Observer,让它们之间互不影响。

总共是有五种Mode:

  • kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
  • UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
  • UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
  • kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,实际是kCFRunLoopDefaultModeUITrackingRunLoopMode的结合。

有这样一个场景,假设自己封装一个无限轮播视图,很有可能会出现这样一种情况:当你滑动轮播视图时,轮播视图的定时器不再起作用,不能通过定时器调整UIScrollView的偏移值。之所以会出项上述现象,是因为主线程的 RunLoop 里有两个 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode。默认情况下是defaultMode,但是当滑动UIScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回。如果想在滑动的时候不让定时器失效,可以使用CommonMode来解决。

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

2.3 Mode 中的 CFRunLoopSourceRef

CFRunLoopSourceRef是事件源,主要有两种分类方式,一种是苹果官方的分类方式,另一种是按照函数调用栈栈分类方式。

2.3.1 官方分类

  • Port-Based Sources(基于端口)
  • Custom Input Sources(自定义)
  • Cocoa Perform Selector Sources

2.3.2 按照函数调用栈分类

  • Source0 :非基于 Port。只包含了一个回调(函数指针),不能主动触发事件。使用时,需先调用 CFRunLoopSourceSignal(source),将 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)唤醒 RunLoop,让其处理这个事件。
  • Source1:基于Port,通过内核和
    其他线程通信,接收、分发系统事件。 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

创建一个按钮,添加点击事件,并在按钮回调事件添加断点,当执行到断点出左侧会出现相关栈调用信息。从上图可以看出:点击事件就是在Sources0中处理的。至于 Source1 主要是用来接收、分发系统事件,然后再分发到Sources0中处理。

2.4 Mode 中的 CFRunLoopTimerRef

CFRunLoopTimerRef 是定时源,你可以简单把它理解为NSTimer。其包含一个时间点和一个回调(函数指针)。当被加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间到时,RunLoop 会执行对应时间点的回调。

2.5 Mode 中的 CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,主要用来监听RunLoop 的状态,主要有以下几种状态。

  • kCFRunLoopEntry : 即将进入RunLoop
  • kCFRunLoopBeforeTimers :即将处理Timer
  • kCFRunLoopBeforeSources:即将处理Source
  • kCFRunLoopBeforeWaiting :即将进入休眠
  • kCFRunLoopAfterWaiting:即将从休眠中唤醒
  • kCFRunLoopExit :即将从RunLoop中退出
  • kCFRunLoopAllActivities:监听全部状态改变

可以通过以下代码验证RunLoop的几种状态:

    // 创建观察者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"监听到RunLoop发生改变---%zd",activity);
    });
    // 添加观察者到当前RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 释放observer
    CFRelease(observer);


 
原文链接:https://www.jianshu.com/p/7bc36b111774
 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值