iOS - Runloop

iOS中的runloop简单的理解就是类似do-while循环的一个机制,用来保持线程处于长期运行状态,而不是让程序运行一次就直接结束了(对比网络的等待请求状态和APP的形成),这类一般被称为Event Loop。

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoopCFRunLoopRef,用于管理这这类 Event Loop :

  • CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
  • NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

runloop 与 线程#

runloop 与 线程 在iOS中是一一对应的,其关系是保存在一个全局的 Dictionary 里。

并且只有主线程默认打开了Runloop,子线程需要手动打开

在线程创建时并不是同时创建runloop,runloop也不允许手动创建。只能通过API获取,通过[NSRunLoop currentRunLoop] - (面向对象API,不是是线程安全的)或者CFRunLoopGetCurrent() - (C语言函数API, 线程安全的)来获取。而runloop在获取runloop时会自动创建(主线程runloop除外,在程序开始时已经被创建了).

1
2
3
4
5
6
7
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}

runLoop 的 相关类#

Core Foundation中关于RunLoop的5个类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef#

runloop的运行模式

  • 一个runloop有若干个model
  • 每个model有若干个(Source/Timer/Observer)
  • 每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
  • 如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。(这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。)

系统默认注册好了五个model。分别是:

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

其中常用的是:
kCFRunLoopDefaultMode
UITrackingRunLoopMode
kCFRunLoopCommonModes

CFRunLoopSourceRef#

runloop的事件源

按照函数调用栈的分类:

  • Source0: event事件,只含有回调(函数指针),需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop
  • Source1: 包含了一个 mach_port 和一个回调,被用于通过内核和其他线程相互发送消息,能主动唤醒 RunLoop 的线程。

CFRunLoopTimerRef#

CFRunLoopTimerRef是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

GCD不受runloop的model影响

CFRunLoopObserverRef#

每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化

1
2
3
4
5
6
7
8
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)observer
{
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});

// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// 释放Observer
CFRelease(observer);
}

CF的内存管理(Core Foundation)

  • 凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release(比如CFRunLoopObserverCreate)
  • release函数:CFRelease(对象);

RunLoop 的 运行过程#

如图:

runloop就是这样的一个do-while循环

Runloop 常用点#

仅仅谈谈我知道的内容

轮播广告#

使用NStimer控制轮播,然后model使用NSDefaultRunLoopMode,因为NSDefaultRunLoopMode的优先级比UITrackingRunLoopMode低,使用当手动滑动的时候会自动停止轮播,停止手动操作后自动还原。
如果使用kCFRunLoopCommonModes则可以定时滚动不受影响

滑动列表界面图片加载控制#

当列表需要加载大量图片的时候,因为渲染在runloop中执行,所以图片一多质量一高就有可能会卡。
方案:

  1. 先把显示代码保存在一个个block,然后存入数组中
  2. 监听runloop循环
  3. 每一次循环在数组中只取出一个block并执行,执行过的代码从数组中remove
  4. 一直持续到数组为空

参考:深入理解RunLoop

如何更好的自主学习? Objective-C对象模型

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×