参考的博客:
iOS RunLoop详解 [iOS开发]Runloop iOS八股文(二十)Runloop探究 iOS RunLoop详解 看苹果官方文档怎么说RunLoop
RunLoop初探
RunLoop是什么?
RunLoop 从字面上来说是跑圈的意思,如果这样理解不免有些肤浅。下面是苹果官方文档的关于RunLoop 的一段说明:
Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
这段话翻译成中文如下:
RunLoop 是与线程息息相关的基本基础结构的一部分。RunLoop 是一个调度任务和处理任务的事件循环。RunLoop 的目的是为了在有工作的时让线程忙起来,而在没工作时让线程进入睡眠状态。
简单的说 RunLoop 是一种高级的循环机制,让程序持续运行,并处理程序中的各种事件,让线程在需要做事的时候忙起来,不需要的话就让线程休眠
在App 运行的过程中,主线程的Runloop 保证了主线程不被销毁从而保证应用的存活,从而能实时接收到用户的响应事件,能够触发定时事件。如果没有Runloop 的话,程序执行完代码就会立马return
RunLoop对象的获取
在iOS 中有2 套关于Runloop 的API ,CoreFoundation 的CFRunloopRef 和Fundationde 的NSRunloop
- (void)runloopApi {
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop run];
[NSRunLoop mainRunLoop];
CFRunLoopRef runLoopSecond = CFRunLoopGetCurrent();
CFRunLoopRun();
CFRunLoopRef runLoopThird = CFRunLoopGetMain();
}
接着我们来看一下CoreFoundation 中这两种获取RunLoop对象的实现:
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL;
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np());
return __main;
}
其都调用了_CFRunLoopGet0 这个函数,后面再进行讲解。
RunLoop与线程
从上面关于RunLoop 的定义我们可以知道,RunLoop 和线程有着密不可分的关系。通常情况下线程的作用是用来执行一个或多个特定的任务,在线程执行完成之后就会退出不再执行任务,RunLoop 这样的循环机制会让线程能够不断地执行任务并不退出
Run loop management is not entirely automatic. You must still design your thread’s code to start the run loop at appropriate times and respond to incoming events. Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop. Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process. 【译】RunLoop 管理并不是完全自动的。需要设计一个线程在合适的时机启动并响应传入的事件,您仍然必须设计线程的代码以在适当的时候启动运行循环并响应传入的事件。 Cocoa 和Core Foundation 都提供RunLoop 对象,以帮助配置和管理线程的RunLoop 。应用程序并不需要显式创建这些对象。每个线程(包括应用程序的主线程)都有一个关联的RunLoop 对象。但是,在子线程中需要显式地运行RunLoop 。在应用程序启动过程中,应用程序框架会自动在主线程上设置并运行RunLoop 。
- 从上面这一段话中我们获取到如下几点信息:
RunLoop 和线程是绑定在一起的,每条线程都有唯一一个与之对应的RunLoop 对象。 - 不能自己创建
RunLoop 对象,但是可以获取系统提供的RunLoop 对象。 - 主线程的
RunLoop 对象是由系统自动创建好的,在应用程序启动的时候会自动完成启动,而子线程中的RunLoop 对象需要我们手动获取并启动。
接下来我们就来探究一下主线程RunLoop的自动启动情况:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
其中的UIApplicationMain 函数内部帮我们开启了主线程的RunLoop UIApplicationMain 内部拥有一个无限循环的代码:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
程序会一直在do-while 循环中执行。
苹果官方对于RunLoop与线程的关系如下图所示: 从上图中可以看出,RunLoop 在线程中不断检测,通过input source 和timer source 接受事件,然后通知线程进行处理事件
RunLoop的结构
RunLoop源码下载地址:RunLoop源码 下方是RunLoop 的结构定义源码:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock;
__CFPort _wakeUpPort;
Boolean _unused;
volatile _per_run_data *_perRunData;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
从上面RunLoop 的源码不难看出,一个RunLoop 对象包含一个线程(_pthread) ,若干个mode(_modes) ,若干个commonMode(_commonModes) 。 不管是mode 还是commonMode 其类型都是CFRunLoopMode ,只是在处理上有所不同,不过在某一时刻Runloop 的mode 是只有一个的(也就是上面的成员变量_currentMode )
在上面的那一众成员变量中,我们可以聚焦观察这三个:
pthread_t _pthread;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
下面我们浅浅总结一下:
Runloop和线程的关系
Runloop 和线程是一对一的关系,每个线程中都有一个唯一的Runloop ,每个Runloop 都对应着一个线程,其中主线程的Runloop 是默认开启的,子线程的Runloop 默认是关闭状态的,如果要在子线程使用Runloop 一定要记得开启Runloop 。这一点从CFRunloop 的数据结构中也能看出。
Runloop和RunloopMode的关系
Runloop 和RunloopMode 是一对多的关系,Runloop 中有多个RunloopMode (注意成员变量_commonModes 的类型为Set )。而在某一时刻Runloop 的mode 是只有一个的(也就是上面的成员变量_currentMode )
RunLoop 有五种运行模式(Mode ):
kCFRunLoopDefaultMode: 是App 的默认Mode ,通常主线程是在这个Mode 下运行UITrackingRunLoopMode: 界面跟踪 Mode ,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode ,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode ,通常用不到kCFRunLoopCommonModes: 这是一个占位用的Mode ,作为标记kCFRunLoopDefaultMode 和UITrackingRunLoopMode 用,并不是一种真正的Mode 。
注意: 在使用NSTimer 的时候,需要将Timer 放在Runloop 中,并且需要指定Mode ,由于是Fundation 框架的API ,在Fundation 中Mode 只有两种NSRunLoopCommonModes、NSDefaultRunLoopMode 。如果使用NSDefaultRunLoopMode ,在滑动ScrollView 的时候,Runloop 会切换到UITrackingRunLoopMode 模式,对应的Timer 会短暂性失效,等到Runloop 再次切换到NSDefaultRunLoopMode 的时候才会起作用。所以我们在添加Timer 的时候最好使用NSRunLoopCommonModes
CFRunLoopModeRef
如下所示是CFRunLoopMode 的源码:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired;
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline;
uint64_t _timerHardDeadline;
};
从CFRunLoopMode 的源码不难看出,一个CFRunLoopMode 对象有唯一一个name ,若干个sources0 事件,若干个sources1 事件,若干个timer 事件,若干个observer 事件和若干port ,RunLoop 总是在某种特定的CFRunLoopMode 下运行的,这个特定的mode 便是_currentMode 。而CFRunloopRef 对应结构体的定义知道一个RunLoop 对象包含有若干个mode ,那么就形成了如下如所示的结构: 上面这个结构体的成员变量中我们可以聚焦于下面几个:
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
简言之就是:RunloopMode 里面有sources0 、sources1 、observer 、timer 组成
CFRunLoopSourceRef-事件源
如下是CFRunLoopSource 的源码:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order;
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0;
CFRunLoopSourceContext1 version1;
} _context;
};
根据苹果官方的定义CFRunLoopSource 是输入源的抽象,分为source0 和source1 两个版本
- source0: 是
App 内部事件,只包含一个函数指针回调,并不能主动触发事件,使用时,你需要先调用CFRunLoopSourceSignal(source) ,将这个source 标记为待处理,然后手动调用CFRunLoopWakeUp(runloop) 来唤醒RunLoop ,让其处理这个事件。 - source1:
source1 包含一个mach_port 和一个函数回调指针。source1 是基于port 的,通过读取某个port 上内核消息队列上的消息来决定执行的任务,然后再分发到sources0 中处理的。source1 只供系统使用,并不对开发者开放。
CFRunLoopTimerRef–Timer事件
CFRunLoopTimer 是定时器。下面是CFRunLoopTimer 的源码:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval;
CFTimeInterval _tolerance;
uint64_t _fireTSR;
CFIndex _order;
CFRunLoopTimerCallBack _callout;
CFRunLoopTimerContext _context;
};
CFRunLoopTimer 是基于时间的触发器,它和NSTimer 可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到RunLoop 时,RunLoop 会注册对应的时间点,当时间点到时,RunLoop 会被唤醒以执行那个回调,同时苹果官方文档也有提到CFRunLoopTimer 和NSTimer 是toll-free bridged 的,这就一位着两者之间可以相互转换。
对于NSTimer : scheduledTimerWithTimeInterval 和RunLoop 的关系是会自动加入NSDefaultRunLoopMode :
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
下方写法的效果和上方一样:
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
CFRunLoopObserverRef–观察者
CFRunLoopObserver 是观察者,监测RunLoop 的各种状态的变化。如下是CFRunLoopObserver 的源码
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities;
CFIndex _order;
CFRunLoopObserverCallBack _callout;
CFRunLoopObserverContext _context;
};
RunLoop 的source 事件源来监测是否有需要执行的任务,而observer 则是监测RunLoop 本身的各种状态的变化,在合适的时机抛出回调,执行不同类型的任务。RunLoop 用于观察的状态如下:
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
};
可以看到除了进入退出外还有所有状态外有4 种运行中的状态。
小结
RunLoop的相关类
与RunLoop 相关的类有5 个:
CFRunLoopRef 代表了RunLoop 的对象CFRunLoopModeRef 是RunLoop 的运行模式CFRunLoopSourceRef 就是RunLoop 模型图中提到的输入源(事件源)CFRunLoopTimerRef 定时源CFRunLoopObserverRef 观察者,监听RunLoop 状态的改变
RunLoop构成小结
RunLoop 是一种高级的循环机制,让程序持续运行,并处理程序中的各种事件,让线程在需要做事的时候忙起来,不需要的话就让线程休眠RunLoop 和线程是绑定在一起的,每条线程都有唯一一个与之对应的RunLoop 对象- 每个
RunLoop 对象都会包含有若干个mode ,每个mode 包含有唯一一个name ,若干个sources0 事件,若干个sources1 事件,若干个timer 事件,若干个observer 事件和若干port ,RunLoop 总是在某种特定的mode 下运行的,这个特定的mode 便是_currentMode
下面这张图很好地展现了RunLoop 的结构:
RunLoop底层实现原理
最上面我们提到了无论是获取当前线程的RunLoop 还是获取主线程的RunLoop ,它们的接口函数里都是调用了_CFRunLoopGet0 ,而且该函数的参数分别是:pthread_self() (获取当前线程的RunLoop 时)和pthread_main_thread_np() (获取主线程的RunLoop 时),由此可见,CFRunLoopGetMain 函数不管是在主线程还是子线程中都可以获取到主线程的RunLoop 。
接下来我们就来看一下_CFRunLoopGet0 函数的源码。
_CFRunLoopGet0
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
{
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS - 1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
上面这段关于CFRunLoopGet0 函数方法的代码并不复杂,我们可以从中得到如下几个信息:
RunLoop 和线程是一一对应的,是以线程为key ,RunLoop 对象为value 存放在一个全局字典中的。- 主线程的
RunLoop 会在初始化全局化字典时创建。 - 子线程的
RunLoop 会在第一次获取时创建。 - 当线程销毁时,对应的
RunLoop 也会随之销毁。
接着我们再梳理一遍RunLoop 的内部运行逻辑,为后面主要源码的讲解做一个铺垫。
The Run Loop Sequence of Events(RunLoop的内部运行逻辑)
该部分内容摘选自:苹果官方文档-RunLoop 每一次运行RunLoop ,线程对应的RunLoop 就会处理挂起的事件,并通知观察者。它执行的顺序如下:
- 通知
Observer 即将进入RunLoop - 通知
Observer 即将处理Timer - 通知
Observer 即将处理Source0 (非端口的输入源) - 处理
Source0 (非端口的输入源) - 如果有
Source1 (基于端口的输入源)准备就绪并等待被触发,立即处理该事件,并跳到步骤9 - 通知
Observer 即将休眠 - 线程休眠(实际上是进入了一个
do-while 循环等待接收端口的消息,接收到时就跳出循环),直到发生以下事件之一:
- 一个事件到达
Source1 (基于端口的输入源) - 一个定时器(
Timer )触发 RunLoop 超时唤醒RunLoop 被手动唤醒(例如添加一个Source0 非端口的输入源) - 通知
Observer 线程刚刚唤醒 - 处理待处理的事件
- 如果用户定义的
Timer 触发了,则处理这个定时器事件并重新启动RunLoop 循环,跳到步骤2 - 如果输入源触发了,则传递事件
- 如果
RunLoop 被手动唤醒,但尚未超时,重新启动RunLoop 循环,跳到步骤2
- 通知
Observer RunLoop 已经退出
下面这张经典的图描述的很好,但介于原图上有几处错误,下图展示的是本人修改之后的:
接着我们来看一下CFRunLoopRun 函数,也就是RunLoop 的启动函数:
void CFRunLoopRun(void) {
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
接着来看CFRunLoopRunSpecific 函数:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode)
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
如上是CFRunLoopRunSpecific 方法的实现代码,这段代码看上去复杂,其实很简单。这个方法需要传入四个参数:
rl :当前运行的RunLoop 对象 modeName :指定RunLoop 对象的mode 的名称 seconds :RunLoop 的超时时间 returnAfterSourceHandled :是否在处理完事件之后返回
从上面的代码我们可以获取到如下几点信息:
RunLoop 运行必须要指定一个mode ,否则不会运行RunLoop - 如果指定的
mode 没有注册事件任务,RunLoop 不会运行 - 通知
observer 进入runloop ,调用 __CFRunLoopRun 方法处理任务,通知observer 退出runloop
上面的处理任务的关键就是调用 __CFRunLoopRun 方法,所以我们接着看 __CFRunLoopRun 方法的实现
__CFRunLoopRun
__CFRunLoopRun 方法的源码如下(这个方法非常繁重),我们可以配合着上方讲解的RunLoop 十条内部运行逻辑来看,其中十条步骤的第一步和第十步都在CFRunLoopRunSpecific 方法中,__CFRunLoopRun 方法中只有第二步到第九步:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
mach_port_name_t dispatchPort = MACH_PORT_NULL;
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) {
seconds = 0.0;
timeout_context->termTSR = 0ULL;
}
else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context);
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
}
else {
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
}
#endif
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
#if DEPLOYMENT_TARGET_WINDOWS
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
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);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
__CFRunLoopRun 方法的源码很长,看上出写了一堆乱七八糟的东西,实际上该方法内部就是一个 大的do-while 循环(其中还嵌套了一个RunLoop 休眠时进入的小do-while 循环),当调用该方法时,线程就会一直留在这个循环里面,直到超时或者手动被停止,该方法才会返回。在这里循环里面,线程在空闲的时候处于休眠状态,在有事件需要处理的时候,处理事件。该方法是整个RunLoop 运行的核心方法。苹果官方文档-RunLoop对于RunLoop 处理各类事件的流程有着详细的描述。
具体的RunLoop 内部运行逻辑的十个步骤的总结见上方总结。
__CFRunLoopServiceMachPort
如果你仔细看过__CFRunLoopRun 方法的代码实现,就会发现在其方法内部有一个内置的循环,这个循环会让线程进入休眠状态,直到收到新消息才跳出该循环,继续执行RunLoop 。这些消息是基于mach port 来进行进程之间的通讯的:
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
Boolean originalBuffer = true;
kern_return_t ret = KERN_SUCCESS;
for (;;) {
mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
msg->msgh_bits = 0;
msg->msgh_local_port = port;
msg->msgh_remote_port = MACH_PORT_NULL;
msg->msgh_size = buffer_size;
msg->msgh_id = 0;
if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
ret = mach_msg(msg,
MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV),
0,
msg->msgh_size,
port,
timeout,
MACH_PORT_NULL);
CFRUNLOOP_WAKEUP(ret);
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
if (MACH_RCV_TOO_LARGE != ret) break;
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
如上是__CFRunLoopServiceMachPort 的源码,该方法接收指定内核端口的消息,并将消息缓存在缓存区,供外界获取。该方法的核心是mach_msg 方法,该方法实现消息的发送或接收。RunLoop 调用这个函数去接收消息,如果没有接收到port 的消息,内核会将线程置于等待状态。
RunLoop事件处理
上面我们探索了RunLoop 运行的核心方法__CFRunLoopRun 的代码,根据官方文档的描述总结了事件处理的流程。源码中显示处理事件主要涉及到如下几个方法:
__CFRunLoopDoObservers :处理通知事件__CFRunLoopDoBlocks :处理block 事件__CFRunLoopDoSources0 :处理source0 事件__CFRunLoopDoSource1 :处理source1 事件__CFRunLoopDoTimers :处理定时器CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE :GCD 主队列
这些方法的实现我们不必关系,但是这些方法在处理事件后如何回调给上层,才是我们需要关心的。比如说__CFRunLoopDoSources0 处理的是系统的事件,那么触发一个UIButton 的点击事件后,查看函数调用栈应该可以知道回到给上层是如何进行的,例子如下图所示: 如上图所示UIButton 的点击事件的函数调用栈,我们可以清楚的看到__CFRunLoopDoSources0 方法的调用,然后调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 回到UIKit 层
关于上述方法回调上层的方法对应如下图所示:
下面我们总结一下RunLoop 的回调流程:
RunLoop回调流程
- 当
App 启动时,系统会默认注册五个Mode 【就是上面那五个】 - 当
RunLoop 进行回调时,一般都是通过一个很长的函数调用出去(call out ),当你在你的代码中断点调试时,通常能在调用栈上看到这些函数。这就是RunLoop 的流程:
{
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
mach_msg() -> mach_msg_trap();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
小结
RunLoop 的运行必定要指定一种mode ,并且该mode 必须注册任务事件。RunLoop 是在默认mode 下运行的,当然也可以指定一种mode 运行,但是只能在一种mode 下运行。RunLoop 内部实际上是维护了一个do-while 循环,线程就会一直留在这个循环里面,直到超时或者手动被停止。RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态,否则线程处理事件
RunLoop应用
RunLoop测试样例(使用NSTimer)
我们使用对RunLoop 添加监听者的方法,同时使用定时器来看一下RunLoop 运行流程中的详细过程:
- (void)viewDidLoad {
[super viewDidLoad];
[self runloopObserver];
[self runloopFunc];
}
- (void)runloopFunc {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)timerAction {
NSThread *thread = [NSThread currentThread];
NSLog(@"当前线程为%@",thread);
}
- (void)runloopObserver {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
CFRunLoopMode str = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"RunLoopMode_%@",str);
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:
NSLog(@"RunLoop其他");
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
@end
运行结果如下: 然后我们想视图上添加了一个scrollerView,在滑动scrollerView时我们可以观察到RunLoop“交接班”的过程,代码例子如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self runloopObserver];
[self runloopFunc];
UIScrollView *scrollViewTest = [[UIScrollView alloc] init];
scrollViewTest.delegate = self;
scrollViewTest.frame = CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height * 0.7);
scrollViewTest.backgroundColor = [UIColor yellowColor];
scrollViewTest.contentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height * 2);
[self.view addSubview:scrollViewTest];
}
运行结果如下:
ImageView延迟显示
当界面中含有UITableView ,而且每个UITableViewCell 里边都有图片。这是当我们滚动UITableView 的时候,如果有一堆的图片需要显示,那么可能出现卡顿的情况
解决这个问题的话我们就可以采用延迟图片的显示的方法,当我们滑动时不要加载图片, 拖动结束在显示,代码如下:
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
上面的代码的效果是:用户点击屏幕,在主线程中,两秒之后显示图片,但是当用户点击屏幕之后如果两秒内开始了tableView 的滑动,就会导致RunLoop 切换到UITrackingRunLoopMode 模式而造成两秒到时也不能显示出来图片,除非等tableView 停止滑动恢复原来的RunLoop 模式,方可显示图片。
常驻线程
开发应用程序的过程中,如果后台操作十分频繁,比如后台播放音乐、下载文件等等,我们希望这条线程在其进程存在时一直常驻内存
我们可以添加一条用于常驻内存的强引用子线程,在该线程的RunLoop 下添加一个Sources ,开启RunLoop
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
[self.thread start];
}
- (void)run1 {
NSLog(@"----run1-----");
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"未开启RunLoop");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void) run2 {
NSLog(@"----run2------");
}
上述的三种开始运行RunLoop的方法也就是三种:
方法名 | 介绍 | 中文翻译 |
---|
run | Unconditionally | 无条件 | runUntilDate | With a set time limit | 设定时间限制 | runMode:beforeDate: | In a particular mode | 在特定模式下 |
- 无条件进入是最简单的做法,但也最不推荐。这会使线程进入死循环,从而不利于控制
RunLoop ,结束RunLoop 的唯一方式是kill它 - 如果我们设置了超时事件,那么
RunLoop 会在处理完事件或超时后结束,此时我们可以重新选择开启RunLoop 。这种方式要优于前一种 - 这是相对来说比较优秀的方式,相比于第二种启动方式,我们可以指定
RunLoop 以哪种模式运行 - 通过查看
Run 方法的文档我们可以指定,其本质就是无限调用runMode:beforeDate 方法,同样的,runUntilDate: 也会重复调用runMode:beforeDate ,区别在于它超时后就不会再调用。也就意味着,只有runMode:beforeDate 方法是单次调用,其他两种都是循环调用
我们必须保证线程不消亡,才可以在后台接受时间处理,所以如果没有实现添加NSPort 或者NSTimer ,会发现执行完run 方法,线程就会消亡,后续再执行touchbegan 方法无效
实现了上面三个方法之一,就可以发现执行完了RunLoop 的run 方法,这个时候再点击屏幕,可以不断执行我们为这个强引用子线程添加的run2 方法,因为线程self.thread 一直常驻后台,等待事件加入其中,然后执行
线程保活
我们上面介绍了线程常驻,这样写相当于虽然该任务执行完了,但是RunLoop 一直卡在这里,也不能去执行别的任务
我们这时如果想要使运行循环退出:
如果使用方法二和方法三来启动RunLoop ,那么在启动时候就可以设置超时时间。然而我们期望“使用RunLoop 进行线程保活",希望对线程和它的RunLoop 有最精准的控制,比如在完成任务后立刻结束,而不是依赖于超时机制
据文档描述,我们有一个CFRunLoopStop() 方法来手动结束一个RunLoop 。 CFRunLoopStop() 方法只会结束当前的runMode:beforeDate: 调用,而不会结束后续的调用
- 我们想要控制
RunLoop 就需要使用runMode:beforeDate 方法,因为其他两种方法一个无法停止,一个只能依赖超时机制 CFRunLoopStop() 方法只会结束当前的一次runMode:beforeDate 的调用
对于这两点,我们有下面的解答:
- 因为
runMode:beforeDate 方法是单次调用,我们需要给他加上一个循环,否则调用一次就结束了,和不使用RunLoop 的效果大同小异 - 循环的条件可以默认设置为
YES ,当调用stop 方法时,执行CFRunLoopStop 方法并且将循环条件改为NO ,就可以是循环停止,RunLoop 退出
@interface ViewController ()
@property (strong, nonatomic) NSThread *aThread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.view addSubview:stopButton];
stopButton.frame = CGRectMake(180, 180, 100, 50);
stopButton.titleLabel.font = [UIFont systemFontOfSize:20];
[stopButton setTitle:@"stop" forState:UIControlStateNormal];
stopButton.tintColor = [UIColor blackColor];
[stopButton addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.aThread = [[NSThread alloc] initWithBlock:^{
NSLog(@"go");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"ok");
}];
[self.aThread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(doSomething) onThread:self.aThread withObject:nil waitUntilDone:NO];
}
- (void)doSomething {
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)stop {
[self performSelector:@selector(stopThread) onThread:self.aThread withObject:nil waitUntilDone:YES];
}
- (void)stopThread {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
运行结果如下: 点击stop按钮之后,线程保活就结束了。
|