小码哥iOS学习笔记第十七天: Runloop基本认识

一、什么是Runloop

  • 顾名思义, Runloop就是运行循环, 在程序运行过程中循环做一些事情

  • 应用范畴

    • 定时器(Timer)、PerformSelector
    • GCD Async Main Queue
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoreleasePool
  • 一个OC程序, main函数是这样的

#import 
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
复制代码
  • 程序打开后, 会一直运行

  • 如果没有Runloop, main函数返回0
#import 
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return 0;
    }
}
复制代码
  • 此时程序打开后, 会直接关闭, 无法一直运行

  • 所以可以写出Runloop的伪代码, 如下图所示

  • do-while是一个死循环, 当没有任何消息发生的时候, 程序处于睡眠状态等待消息发生
  • 当消息产生后, 就会处理消息, 接着继续睡眠等待, 程序并不会马上退出,而是保持运行状态
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
......
复制代码

二、Runloop对象

  • 在iOS中, 有两套API来访问和使用Runloop
    • Foundation: NSRunLoop
    • Core Foundation: CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    • NSRunLoop是基于CFRunLoopRef的一层OC封装
    • CFRunLoopRef是开源的 opensource.apple.com/tarballs/CF…

三、RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里, 线程作为key, RunLoop对象做为Value
  • 线程刚创建时并没有RunLoop对象, RunLoop会在第一次获取它时创建
  • RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建), 子线程默认没有开启RunLoop

四、获取RunLoop对象

  • 可以通过FoundationCore Foundation来获取RunLoop

1、Foundation

  • 获取当前线程的RunLoop
[NSRunLoop currentRunLoop];
复制代码
  • 获取主线程的RunLoop
[NSRunLoop mainRunLoop];
复制代码

2、Core Foundation

  • 获取当前线程的RunLoop
CFRunLoopGetCurrent()
复制代码
  • 获取主线程的RunLoop
CFRunLoopGetMain()
复制代码

五、RunLoop相关的类

  • RunLoop相关的类有五个
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
复制代码
  • CFRunLoopRef实际类型是__CFRunLoop

  • 主要成员结构有下面几个
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};
复制代码
  • _modes中存放的是CFRunLoopModeRef类型数据, 其中就有_currentMode, 只不过_currentMode是当前使用的mode

  • CFRunLoopModeRef的结构如下

  • 主要成员变量有下面几个
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
复制代码
  • 其中的_sources0_sources1CFRunLoopSourceRef类型数据
  • 其中的_observersCFRunLoopObserverRef类型数据
  • 其中的_timersCFRunLoopTimerRef类型数据

六、CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的运行模式
  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
  • RunLoop启动时只能选择其中一个Mode,作为currentMode
  • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    • 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
  • 如果Mode里没有任何Source0/Source1/Timer/ObserverRunLoop会立马退出

常见的两种CFRunLoopModeRef

  • kCFRunLoopDefaultModeNSDefaultRunLoopMode): App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

七、RunLoop的运行逻辑

  • Source0
    • 触摸事件处理
    • performSelector:onThread:
  • Source1
    • 基于Port的线程间通信
    • 系统事件捕捉
  • Timers
    • NSTimer
    • performSelector:withObject:afterDelay:
  • Observers
    • 用于监听RunLoop的状态
    • UI刷新(BeforeWaiting)
    • Autorelease pool(BeforeWaiting)
  • 可以打印, 当手指点击屏幕时函数的调用栈

  • 可以看到屏幕触摸事件是Source0处理的

八、监听RunLoop状态

  • 我们可以通过给RunLoop添加Observer的方式监听RunLoop的状态, 使用下面这个函数
/**
 给RunLoop添加Observer

 @param rl 目标RunLoop
 @param observer 需要添加的Observer
 @param mode 监听状态
 */
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
复制代码
  • 可以通过下面的函数创建Observer
/**
 创建Observer

 @param allocator 分配器
 @param activities 需要监听的状态
 @param repeats 是否重复监听
 @param order 顺序
 @param callout 回调函数
 @param context 附加对象
 @return 创建好的Observer
 */
CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
复制代码
  • RunLoop的状态有下面几种
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码

1、监听点击事件

  • 创建一个Observer, 并将其添加到Runloop中, 监听屏幕点击事件

  • 运行程序就可以看到正在监听RunLoop的状态, 当最后没有事件时, Runloop进入睡眠状态

  • 清空打印, 点击屏幕, 有如下结果, 可以看到先进入sources状态, 然后执行点击事件

2、监听滚动事件

  • 监听UITextView的滚动, 查看RunLoopcurrentMode

  • 创建Observer的另一种方式, 使用block监听RunLoop状态

  • view上添加一个UITextView

  • 运行程序, 滚动UITextView, 有如下打印

  • 可以看到滚动之前, 先退出kCFRunLoopDefaultMode, 进入UITrackingRunLoopMode
  • 等滚动结束, 会先退出UITrackingRunLoopMode, 进入kCFRunLoopDefaultMode

转载于:https://juejin.im/post/5c8b6e555188251bde6be463

你可能感兴趣的:(小码哥iOS学习笔记第十七天: Runloop基本认识)