参考的博客:
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,启动完成后就不再使用,会切换到kCFRunLoopDefaultModeGSEventReceiveRunLoopMode: 接受系统事件的内部 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按钮之后,线程保活就结束了。
|