RunLoop的概念
首先我们通过Xcode创建一个Command Line Tool project。发现Xcode给我们自动生成了main.m,包含如下代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
运行后,控制台输出Hello,World!然后程序就退出了。
然而,我们在创建的iOS程序,main.m中包含的代码如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
运行后,程序并不会退出,而是一直处于运行状态,等待用户响应。当我们把main函数稍作修改,如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
int i = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
return i;
}
}
给return i;
这一行加上断点。发现并不会被断点断住。说明并没有执行到这一步,即上一步还没有结束。为什么会这样呢,那就引出了今天的主角:RunLoop!因为UIApplicationMain函数内部帮我创建了一个RunLoop “运行循环”,来保证线程不会退出,能随时处理事件和消息。大致的代码逻辑:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
会有一个do while
循环来等待message,并处理message,只有当while条件不满足时(比如传入 quit 的消息),才会退出循环,让函数返回。而RunLoop内对其进行了进一步的优化:它能很好的管理事件和消息,并且让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。下文会对其进行讲解。
在继续之前,我们可以先下载CFRunLoopRef源码,来进行更好的分析和学习。下载地址:https://opensource.apple.com/tarballs/CF/。
通过CoreFoundation框架和Foundation框架,我们发现OSX/iOS 系统中,提供了两个这样的对象:CFRunLoopRef 和 NSRunLoop。
CFRunLoopRef提供了纯C语言的api,所以是线程安全。而NSRunLoop是基于CFRunLoopRef的封装,符合了面向对象的特点,但是不是线程安全的。
RunLoop的创建过程
CFRunLoop.c文件的部分源代如下:
static CFMutableDictionaryRef __CFRunLoops = NULL;
static pthread_t kNilPthreadT = (pthread_t)0;
// t==0 is a synonym for "main thread" that always works //当t==0时代表主线程
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//进行加锁操作
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 第一次进入时,初始化全局dict,并先为主线程创建一个 RunLoop。并将mainLoop添加到dict中
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);
}
//通过线程直接从dict中获取loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//如果获取失败,通过线程创建一个loop,
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//再次确认没有loop,就添加到dict中。
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
//获取当前线程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
//获取主线程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
整体的流程可以概括为以下几步:
- 通过_CFRunLoopGet0函数传入一条线程。
- 判断线程是否为主线程并且判断是否已经存在__CFRunLoops(全局CFMutableDictionaryRef)。
- 如果不存在,说明第一次进入,初始化全局dict,并先为主线程创建一个 RunLoop。并将mainLoop添加到dict中。
- 如果__CFRunLoops存在,会通过对应线程在全局的__CFRunLoops中查找对应的RunLoop。
- 如果对应RunLoop不存在,会创建一个新的RunLoop,并添加到__CFRunLoops中。
- 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
- 返回RunLoop。
我们只能通过CFRunLoopGetMain函数或者CFRunLoopGetCurrent函数来获取RunLoop,通过上面的源代码我们发现,无论是CFRunLoopGetMain函数还是CFRunLoopGetCurrent函数,都是通过对应的线程获取对应的RunLoop,线程和RunLoop是一一对应的,不会重复创建。在主线程,系统会帮我们创建RunLoop,来处理事件。而子线程RunLoop并不会默认开启。所有,子线程操作完成后,线程就被销毁了,如果我们想线程不被销毁,得主动获取一个RunLoop,并且在RunLoop中添加Timer/Source/Observer其中的一个。
RunLoop的内部结构
在 CoreFoundation 里面关于 RunLoop 有5个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopObserverRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
CFRunLoopMode 和 CFRunLoop 的源码结构大致如下:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
...
};
可以看出CFRunLoop和CFRunLoopMode有如下关系:
RunLoop内部结构图每个RunLoop中有若干个Mode,每个Mode中又存在着若干个Observer、Source和Timer,每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
CFRunLoopObserverRef:是观察者,我们可以通过CFRunLoopObserverCreateWithHandler函数来创建一个观察者(函数会有一个block回调),来对RunLoop进行观察,当RunLoop状态变化时,会触发block回调,回调会返回对应的状态,我们可以在回调里做相应做的操作。可以观察到的状态有:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 1 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 2 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2),// 4 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5),// 32 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 64 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 128 即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU// 所有状态类型
};
我们来看下具体的举例(Demo地址)大家不妨下载看看:
- (void)observer {
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"监听到即将进入RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeTimers:
NSLog(@"监听到即将处理Timer------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeSources:
NSLog(@"监听到即将处理Source------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"监听到即将进入睡眠------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopAfterWaiting:
NSLog(@"监听到即将从睡眠中醒来------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
case kCFRunLoopExit:
NSLog(@"监听到即将从退出RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
break;
default:
break;
}
});
/*
CFRunLoopAddObserver函数有三个参数:
* 第一个:传入一个RunLoop,CFRunLoopGetCurrent()获取当前的RunLoop
* 第二个:传入一个观察者,observer就是新创建的观察者
* 第三个:传入Mode,kCFRunLoopCommonModes指定要监听的Mode
*/
// 添加观察者到RunLoop:来监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
// 释放Observer
CFRelease(observer);
}
除了在viewDidLoad里面调用了[self observer];
方法,还在Main.storyboard中添加了UITextView。下面我们拿打印结果阐述。
运行后的模拟器截图:
模拟器截图刚刚触发监听的截图:
刚刚触发监听的截图我们发现RunLoop在不停的处理Timer事件和Source事件。虽然我们没有主动添加Timer和Source事件,但是系统会添加Timer和Source,比如:模拟器的时间发生变化时就会触发kCFRunLoopBeforeTimers
回调(每分钟),如下图:
当视图显示完成稳定后无事件的截图(看具体的时间点发现是在整分钟的时候触发的回调):
稳定后无事件的截图
当无其他点击等事件时,会进入睡眠状态,有事件时会立刻苏醒过来处理事件。
拖拽textView时的截图:
拖拽textView时的截图发现在拖拽textView的时候,当前RunLoop的Mode[[NSRunLoop currentRunLoop] currentMode]
变成了从kCFRunLoopDefaultMode
变成了UITrachingRunloopMode
,RunLoop也触发了kCFRunLoopExit
(即将退出RunLoop)的回调和kCFRunLoopEntry
(即将进入RunLoop)的回调,也就是:退出了当前RunLoop,进入了一个新的RunLoop,也就验证了上面所说的:每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 当前RunLoop,再重新指定一个 Mode 进入。
如果到这里看不懂,没有关系,后面会对RunLoop 的内部逻辑进行分析。看了后面的分析在回过头来看就能明白了。
CFRunLoopSourceRef:是事件产生的地方。Source分为Source0 和 Source1。
- Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 。
- Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
CFRunLoopTimerRef:基于时间的触发器,CFRunLoopTimerRef是Core Foundation提供的基础定时器,NSTimer则是建立在CFRunLoopTimerRef之上的高层组件。当Timer被加入到RunLoop时,RunLoop会注册对应的时间点,当达到时间时,RunLoop会被唤醒,执行创建Timer时的回调。
RunLoop 的 Mode
我们在viewDidLoad中通过下面代码打印当前RunLoop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSLog(@"%@",runloop);
我们通过打印结果和分析结合来看(可以先到下面看分析,再回到上面找对应的打印结果或者下载demo,在控制栏对照来看。)
打印结果如下:
2018-03-13 23:14:28.671838+0800 runloop[30490:55545179] <CFRunLoop 0x6000001f2400 [0x110675bb0]>{wakeup port = 0x1b03, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x6000002552d0 [0x110675bb0]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x1119f9820 [0x110675bb0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x11064b7f0 [0x110675bb0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x60400024e9a0 [0x110675bb0]>{type = mutable set, count = 16,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}
4 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
6 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
7 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
8 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
9 : <CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}
10 : <CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
0 : <0x7f874a802048>
)}}
11 : <CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
0 : <0x7f874a802048>
)}}
12 : <CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}
13 : <CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}
16 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
18 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
20 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
21 : <CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}
22 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
}
,
modes = <CFBasicHash 0x600000255420 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
2 : <CFRunLoopMode 0x60400019de90 [0x110675bb0]>{name = UITrackingRunLoopMode, port set = 0x2603, queue = 0x604000159910, source = 0x60400019e100 (not fired), timer port = 0x2a03,
sources0 = <CFBasicHash 0x60400024e8b0 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
}
,
sources1 = <CFBasicHash 0x60400024e9d0 [0x110675bb0]>{type = mutable set, count = 5,
entries =>
0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
"<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
"<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
"<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
),
timers = (null),
currently 542646869 (1754787729427038) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
3 : <CFRunLoopMode 0x60400019e1d0 [0x110675bb0]>{name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x6040001599c0, source = 0x60400019e2a0 (not fired), timer port = 0x2e03,
sources0 = <CFBasicHash 0x60400024ea60 [0x110675bb0]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
}
,
sources1 = <CFBasicHash 0x60400024ea90 [0x110675bb0]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopSource 0x604000179440 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
}
,
observers = (null),
timers = (null),
currently 542646869 (1754787749292029) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
4 : <CFRunLoopMode 0x60000019e370 [0x110675bb0]>{name = kCFRunLoopDefaultMode, port set = 0x1c03, queue = 0x6000001595a0, source = 0x60000019e440 (not fired), timer port = 0x1e03,
sources0 = <CFBasicHash 0x60400024ea00 [0x110675bb0]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
}
,
sources1 = <CFBasicHash 0x60400024ea30 [0x110675bb0]>{type = mutable set, count = 5,
entries =>
0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
}
,
observers = (
"<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
"<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
"<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
"<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
"<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
),
timers = <CFArray 0x6000002a00c0 [0x110675bb0]>{type = mutable-small, count = 2, values = (
0 : <CFRunLoopTimer 0x600000179980 [0x110675bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 542646863 (-5.80513895 @ 1754781946507642), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x10f493d9c / 0x110d1c92d) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x60000026f400>}
1 : <CFRunLoopTimer 0x60400017adc0 [0x110675bb0]>{valid = Yes, firing = No, interval = 0.5, tolerance = 0, next fire date = 542646863 (-5.657637 @ 1754782094433008), callout = (NSTimer) [UITextSelectionView caretBlinkTimerFired:] (0x10f4a8acb / 0x10f1d6536) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x604000438880>}
)},
currently 542646869 (1754787749365050) / soft deadline in: 1.84467441e+10 sec (@ 1754781946507642) / hard deadline in: 1.84467441e+10 sec (@ 1754781946507642)
},
5 : <CFRunLoopMode 0x60400019ed30 [0x110675bb0]>{name = kCFRunLoopCommonModes, port set = 0x3f0f, queue = 0x604000159bd0, source = 0x60400019eac0 (not fired), timer port = 0x570f,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 542646869 (1754787752303309) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
},
}
}
从上往下看发现结果和上面说的CFRunLoopMode 和 CFRunLoop 的源码结构是对应上的:
-
current mode = kCFRunLoopDefaultMode
:现在处于kCFRunLoopDefaultMode下。 -
common modes
:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。 上面我们可以看到被标记为“Common”属性的有2种Mode(count = 2):UITrackingRunLoopMode
kCFRunLoopDefaultMode
-
common mode items
:count = 16 ,包含了所有被添加到Mode的Source/Observer/Timer。 -
modes
:所有被添加到RunLoop中的RunLoopMode,count = 4,包含了四个Mode如下:-
name = UITrackingRunLoopMode
:scrollView滚动时候会切换到这种Mode,上文讲Observer的具体demo中,在打印Observer监听时,当拖拽textView的时候就会切换到这种Mode。从上面打印的RunLoop里面的UITrackingRunLoopMode
,可以看出它有包含了sources0(count = 4包含了4个Source0)/sources1(count = 5包含了5个Source1)/observers(7个Observer)/timers(没有Timer)。 -
name = GSEventReceiveRunLoopMode
: 苹果接受系统事件的内部 Mode,通常用不到。从上面打印的RunLoop里面的GSEventReceiveRunLoopMode
,可以看出它仅仅包含了一个Source0和一个Source1。 -
name = kCFRunLoopDefaultMode
:App的默认 Mode,通常主线程是在这个 Mode 下运行的,从上面打印的RunLoop里面的kCFRunLoopDefaultMode
,可以看出它包含了4个Source0、5个Source1、7个Observer和2个Timer。 -
name = kCFRunLoopCommonModes
:可以看到它里面sources0/sources1/observers/timers都是null,可见这是一个占位的 Mode,没有实际作用。 - 其实还有一种Mode,这里没有被添加到当前的RunLoop
UIInitializationRunLoopMode
:它在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用。所以在这里看不到这个Mode。
-
在上文讲Observer的具体demo中,有如下代码:
// 添加观察者到RunLoop:来监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRunLoopAddObserver函数有三个参数:
- 第一个:传入一个RunLoop,CFRunLoopGetCurrent()获取当前的RunLoop
- 第二个:传入一个观察者,observer就是新创建的观察者
- 第三个:传入Mode,kCFRunLoopCommonModes指定要监听的Mode
其中有个kCFRunLoopCommonModes
就是指被标记成“Common”的Mode,上面打印和分析发现,其实就是指UITrackingRunLoopMode
和kCFRunLoopDefaultMode
这两种Mode。所以在拖拽textView的时候和不拖拽的时候都能收到回调,如果将kCFRunLoopCommonModes
改为kCFRunLoopDefaultMode
,会发现,当拖拽的时候收不到回调。大家可以自己试试。
和将NSTimer添加到RunLoop时,指定Mode回调是一样的道理,这里就不再阐述了。已经够啰嗦了。。。
AutoreleasePool的创建和释放时机
在上面对RunLoop的打印中,我们发现系统添加了两个Observer
-
<CFRunLoopObserver 0x60400013edc0>
:activities = 0x1, repeats = Yes, order = -2147483647 它的activities = 0x1,而0x1转换为10进制1,就是表示监听的kCFRunLoopEntry
-
<CFRunLoopObserver 0x60400013ec80>
:activities = 0xa0, repeats = Yes, order = 2147483647 它的activities = 0xa0,而0xa0转换为10进制160,发现没有160这个值的状态,其实它是kCFRunLoopBeforeWaiting
|kCFRunLoopExit
,也就是32+128
它们都触发了_wrapRunLoopWithAutoreleasePoolHandler
回调。
第一个Observer监听kCFRunLoopEntry
(即将进入RunLoop)其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池,其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了kCFRunLoopBeforeWaiting
() 其回调内会调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的AutoreleasePool并创建新AutoreleasePool;kCFRunLoopExit
(即将退出RunLoop) 其回调内会调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
前几天有写一篇iOS内存管理的文章,里面有讲AutoreleasePool的内部实现。
RunLoop 的内部逻辑
根据CoreFoundation框架RunLoop源码,RunLoop 内部的逻辑官方文档如下:
苹果官方文档RunLoop内部逻辑郭大神描述的内部逻辑如下:
RunLoop内部逻辑大体上他们是一致的。
其内部代码整理如下:
/// 用DefaultMode启动
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的实现
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里没有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 触发 Source0 (非port) 回调。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 执行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
/// • 一个基于 port 的Source 的事件。
/// • 一个 Timer 到时间了
/// • RunLoop 自身的超时时间到了
/// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,处理消息。
handle_msg:
/// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,执行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 进入loop时参数说处理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部调用者强制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
PerformSelecter
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。我们可以在没有获取RunLoop的子线程进行测试验证。viewDidLoad中调用performSelectorTest方法。
- (void)performSelectorTest {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
self.thread = thread;
[thread start];
}
- (void)run {
// [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
[self performSelector:@selector(test) withObject:nil afterDelay:2.0];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
- (void)test {
NSLog(@"-----------------");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
当我们把下面代码注释就不会触发test方法了。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
这里有个疑问:
看郭大神的博客说:performSelector:onThread:也是需要RunLoop,但是我在子线程执行的run 方法中执行[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
而且没有主动获取RunLoop的情况下,也是调用test方法。知道的大神指导下。
RunLoop其他举例
UIImageView在NSDefaultRunLoopMode下展示图片
// 只在NSDefaultRunLoopMode模式下显示图片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"image"] afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
创建常驻子线程
//在子线程中获取并启动添加了Source的RunLoop
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
除了获取当前线程的RunLoop外,还得添加Source或者Timer。并且调用run方法。要不然RunLoop中没有Source和Timer,会直接退出当前RunLoop不会进入循环。
欢迎指导沟通!
参考文献:
官方文档
深入理解RunLoop
RunLoop问题集
网友评论