Runloop 是和线程紧密相关的一个基础组件,是不少线程有关功能的幕后功臣。 本文将从如下几个方面来总结runloop:html
runloop
的run
方法源码以下所示,是一个do..while循环服务器
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
复制代码
runloop
其实是一个对象,这个对象提供了一个入口函数。在iOS系统里,下面的这些都有使用runloop,经过断点查看堆栈能够看到调用的方法名:网络
block应用: CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK多线程
调用timer: CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTIONapp
响应source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION框架
响应source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTIONsocket
GCD主队列: CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUEasync
observer源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION函数
timer
的
block
里添加断点,而后左边箭头指示的按钮不选中(默认是选中的),能够看到runloop的调用信息
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
源码以下:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
if (func) {
func(timer, info);
}
getpid(); // thwart tail-call optimization
}
复制代码
关于上面总结的其余几种调用的runloop方法名,均可以用上面的这种调试方式查看一下。
[[NSRunLoop currentRunLoop] run]
)input source
和
timer source
接受事件,而后在线程中处理事件。
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()
,这里是传一个主线程pthread_main_thread_np()
进去,以下定义了它是主线程
#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
复制代码
还有一个获取当前线程runloop的方法:一样是调用了_CFRunLoopGet0
,只不过传进去的是当前线程pthread_self()
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
复制代码
接下来看获取线程runloop的函数_CFRunLoopGet0
(包括主线程和子线程)的源码
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
//根据线程获取runloop
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
//若是存储RunLoop的字典不存在
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//建立主线程的RunLoop
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);
__CFSpinLock(&loopsLock);
}
//字典里找runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFSpinUnlock(&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);
}
}
复制代码
若是当前存储的字典容器不存在,首先就建立了一个容器CFMutableDictionaryRef
可变字典
第二步使用主线程建立了一个主线程runloopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
第三步CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
把主线程和它的runloop用key-value形式保存在这个CFMutableDictionaryRef
字典容器里
以上说明,第一次进来的时候,不论是getMainRunloop仍是get子线程的runloop,主线程的runloop老是会被建立
再看到CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
,能够用线程
把保存在字典里的runloop
取出来
若是字典里没有找到runloop
,就根据当前的子线程建立一个新的runloop
对象并保存到字典里
最后一步if (pthread_equal(t, pthread_self())) {...}
判断当前的线程是否是传递进来的线程,若是是则建立一个回调,若是线程销毁,就销毁当前的runloop
这里验证了上面的结论1和2: runloop和线程是一一对应的(字典保存)。 runloop在首次被线程获取时建立(而且: 无论获取的是主线程runloop仍是子线程runloop,老是会建立主线程的runloop),在线程结束时被销毁(经过回调销毁)
在AppDelegate
打断点,能够看到主线程是有调用__CFRunloopRun
方法的,因此证实了上面的结论三: 主线程是默认开启runloop
的 ![]user-gold-cdn.xitu.io/2019/10/16/…) 测试runloop
代码以下
- (vod)viewDidLoad {
super viewDidLoad];
DLThread *thread = [[DLThread alloc]initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
[NSTimer scheduledTimerWithTimeInterval:1repeats:YES block:^(NSTimer * _Nonnul) {
NSLog(@"timer");
}];
}];
thread.name = @"Test";
[thread start];
复制代码
DLThread.m
里只写了以下代码
-(void)dealloc{
NSLog(@"线程销毁了");
}
复制代码
运行上面的代码,发现timer
并无打印,说明子线程里开启timer
没成功,而后添加了代码运行当前线程的runloop,以下所示:
DLThread *thread = [[DLThread alloc] initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
}];
[[NSRunLoop currentRunLoop] run];
}];
thread.name = @"Test";
[thread start];
复制代码
发现timer
一直在打印了,这里证实了两个结论: timer
的运行是和runloop
有关的,子线程的runloop
是须要手动开启的
那么如何中止timer
呢?新增了一个标记值isStopping
用来退出线程
DLThread *thread = [[DLThread alloc] initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"timer");
if(self.isStopping){
[NSThread exit];
}
}];
[[NSRunLoop currentRunLoop] run];
}];
thread.name = @"Test";
[thread start];
复制代码
运行发现,在线程销毁后,timer
也中止了,这里侧面证实了上面的结论二: runloop
是在线程结束时销毁的
在runloop源码里须要探索的:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息能够唤醒runloop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //RunLoop对应的线程
uint32_t _winthread; //
CFMutableSetRef _commonModes; //存储的是字符串,记录全部标记为common的mode
CFMutableSetRef _commonModeItems;//存储全部commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; //当前运行的mode
CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
struct _block_item *_blocks_head; //doblocks的时候用到
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
复制代码
能够看到,其实runloop就是一个结构体对象,里面包含了一个线程,一个当前正在运行的mode, N个mode,N个commonMode。
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode的名称
Boolean _stopped; //mode是否被终止
char _padding[3];
CFMutableSetRef _sources0; //sources0
CFMutableSetRef _sources1; //sources1
CFMutableArrayRef _observers; //通知
CFMutableArrayRef _timers; //定时器
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存全部须要监听的port,好比_wakeUpPort,_timerPort都保存在这个数组中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
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; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
复制代码
一个CFRunLoopMode
对象有一个name,N个source0、N个source一、timer、observer和port,可见事件都是由Mode
在管理,而RunLoop
管理Mode
。
它们之间的关系以下图:
mode
是容许定制的,不过至少要包含一个mode item
(source/timer/observer)。 同一个mode item
能够被多个mode持有
苹果公开的三种 RunLoop Mode:
还有两种mode,只需作了解便可:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; ////用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
复制代码
CFRunloopSourceRef
是runloop的数据源抽象类对象(protocol),由源码能够看到共用体(union:在相同的内存位置存储不一样的数据类型),可见Source分为两类:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
复制代码
source0: 处理App内部事件、APP本身负责管理(触发)例如:UIEvent CFSocket。 打断点基本都会看到它。
source0
是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。
CFRunLoopSourceSignal
(source)将这个事件标记为待处理
CFRunLoopWakeUp
来唤醒runloop,让他处理事件
自定义source实现步骤:
CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode)
CFRunLoopSourceSignal
CFRunLoopWakeUp
CFRunLoopRemoveSource
CFRelease(rlp)
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
复制代码
source1:
由runloop和 Mach port管理,Mach port驱动,包含一个 mach_port和一个回调(函数指针),被用于经过内核和其余线程相互发送消息。
它可以主动唤醒RunLoop(由操做系统内核进行管理,例如: CFMachPort,CFMessagePort)
还容许实现本身的Source,但通常不会这么作
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
复制代码
它是一个观察者,可以监听Runloop的状态改变,能够向外部报告runloop状态的更改,框架中不少机制都由它触发(如CAAnimation)
在CFRunloop.h
文件里能够看到observer监听的状态以下:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码
正好和下图runloop流程里的observer所对应:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
复制代码
CFRunLoopTimer
是定时器,能够在设定的时间点抛出回调CFRunLoopTimer
和NSTimer
是toll-free bridged的,能够相互转换CFRunLoopTimer
的封装有三种: NSTimer,performSelector和CADisplayLink+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti
invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti
invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument
afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
复制代码
简单总结了这三种timer,以下图:
void CFRunLoopRun(void) { /* DOES CALLOUT */
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) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
复制代码
CFRunLoopRun
和CFRunLoopRunInMode
都调用了CFRunLoopRunSpecific
函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
/// 首先根据modeName找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
/// 通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
复制代码
上面的源码是简化代码后的源码,实际源码复杂一些,根据源码可得出以下结论:
__CFRunLoopRun
函数/// 核心函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
/// 通知 Observers: 即将处理timer事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
/// 通知 Observers: 即将处理Source事件
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
/// 处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 处理sources0返回为YES
if (sourceHandledThisLoop) {
/// 处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
/// 判断有无故口消息(Source1)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
/// 处理消息
goto handle_msg;
}
/// 通知 Observers: 即将进入休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
/// 等待被唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
/// 通知 Observers: 被唤醒,结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if (被Timer唤醒) {
/// 处理Timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
} else if (被GCD唤醒) {
/// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else if (被Source1唤醒) {
/// 被Source1唤醒,处理Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
/// 处理block
__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);
return retVal;
}
复制代码
以上是runloop
核心函数的简写源码(比较清晰易懂)点击下载runloop源码:密码 3kww 还有一个监听唤醒端口消息的函数__CFRunLoopServiceMachPort
比较重要,系统内核将这个线程挂起,停留在mach_msg_trap状态,等待接受 mach_port(用于唤醒的端口) 的消息。线程将进入休眠, 直到被其余线程或另外一个进程的某个线程向该端口发送mach_msg消息唤醒
/** * 接收指定内核端口的消息 * * @param port 接收消息的端口 * @param buffer 消息缓冲区 * @param buffer_size 消息缓冲区大小 * @param livePort 暂且理解为活动的端口,接收消息成功时候值为msg->msgh_local_port,超时时为MACH_PORT_NULL * @param timeout 超时时间,单位是ms,若是超时,则RunLoop进入休眠状态 * * @return 接收消息成功时返回true 其余状况返回false */
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 (;;) { /* In that sleep of death what nightmares may come ... */
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(); }
//经过mach_msg发送或者接收的消息都是指针,
//若是直接发送或者接收消息体,会频繁进行内存复制,损耗性能
//因此XNU使用了单一内核的方式来解决该问题,全部内核组件都共享同一个地址空间,所以传递消息时候只须要传递消息的指针
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);
//接收/发送消息成功,给livePort赋值为msgh_local_port
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
//MACH_RCV_TIMEOUT
//超出timeout时间没有收到消息,返回MACH_RCV_TIMED_OUT
//此时释放缓冲区,把livePort赋值为MACH_PORT_NULL
if (MACH_RCV_TIMED_OUT == ret) {
if (!originalBuffer) free(msg);
*buffer = NULL;
*livePort = MACH_PORT_NULL;
return false;
}
//MACH_RCV_LARGE
//若是接收缓冲区过小,则将过大的消息放在队列中,而且出错返回MACH_RCV_TOO_LARGE,
//这种状况下,只返回消息头,调用者能够分配更多的内存
if (MACH_RCV_TOO_LARGE != ret) break;
//此处给buffer分配更大内存
buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
if (originalBuffer) *buffer = NULL;
originalBuffer = false;
*buffer = realloc(*buffer, buffer_size);
}
HALT;
return false;
}
复制代码
当一个硬件事件(触摸/锁屏/摇晃/加速)发生后,首先有IOKit.framework
生成一个IOHIDEvent
事件并由SpringBoard
接受,以后由mach port
转发给须要的App进程。
苹果注册了一个 Source1 来接受系统事件,经过回调函数触发Source0(因此Event其实是基于Source0)的,调用_UIApplicationHandleEventQueue()
进行应用内部的分发。 _UIApplicationHandleEventQueue()
会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。
当上面的 _UIApplicationHandleEventQueue()
识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End
系列回调打断。随后系统将对应的 UIGestureRecognizer
标记为待处理。
苹果注册了一个 Observer 监测 BeforeWaiting
(Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver()
,其内部会获取全部刚被标记为待处理的GestureRecognizer
,并执行GestureRecognizer
的回调。
当有 UIGestureRecognizer
的变化(建立/销毁/状态改变)时,这个回调都会进行相应处理。
当UI发生改变时(Frame变化,UIView/CALayer的结构变化)时,或手动调用了UIView/CALayer的setNeedsLayout/setNeedsDisplay
方法后,这个UIView/CALayer就被标记为待处理。
苹果注册了一个用来监听BeforeWaiting
和Exit
的Observer,在他的回调函数里会遍历全部待处理的UIView/CALayer
来执行实际的绘制和调整,并更新UI界面。
主线程Runloop注册了两个Observers,其回调都是_wrapRunloopWithAutoreleasePoolHandler
Observers1
监听Entry
事件: 优先级最高,确保在全部的回调前建立释放池,回调内调用 _objc_autoreleasePoolPush()
建立自动释放池
Observers2
监听BeforeWaiting
和Exit
事件: 优先级最低,保证在全部回调后释放释放池。BeforeWaiting
事件:调用_objc_autoreleasePoolPop()
和_objc_autoreleasePoolPush()
释放旧池并建立新池,Exit
事件: 调用_objc_autoreleasePoolPop()
,释放自动释放池
给ImageView
加载图片的方法用PerformSelector
设置当前线程的RunLoop的运行模式kCFRunLoopDefaultMode
,这样滑动时候就不会执行加载图片的方法 [self.imgView performSelector:@selector(setImage:) withObject:cellImg afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
+timerWihtTimerInterval...
建立timer[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]
把timer加到当前runloop,使用占位模式runloop run/runUntilData
手动开启子线程runloop// 得到队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 建立一个定时器(dispatch_source_t本质仍是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置定时器的各类属性(几时开始任务,每隔多长时间执行一次)
// GCD的时间参数,通常是纳秒(1秒 == 10的9次方纳秒)
// 比当前时间晚1秒开始执行
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
//每隔一秒执行一次
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
});
// 启动定时器
dispatch_resume(self.timer);
复制代码
dispatch_async(dispatch_get_main_queue)
使用到了RunLoop
libDispatch
向主线程的Runloop
发送消息将其唤醒,并从消息中取得block,并在回调__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
里执行这个block
使用 NSURLConnection
时,你会传入一个 Delegate,当调用了 [connection start]
后,这个 Delegate
就会不停收到事件回调。
start
这个函数的内部会会获取 CurrentRunLoop
,而后在其中的 DefaultMode
添加了4个 Source0
(即须要手动触发的Source)。 CFMultiplexerSource
是负责各类 Delegate 回调的,CFHTTPCookieStorage
是处理各类 Cookie 的。
当开始网络传输时,咱们能够看到 NSURLConnection 建立了两个新线程:com.apple.NSURLConnectionLoader
和 com.apple.CFSocket.private
。其中 CFSocket 线程是处理底层 socket 链接的。NSURLConnectionLoader
这个线程内部会使用 RunLoop 来接收底层 socket 的事件,并经过以前添加的 Source0 通知到上层的 Delegate。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
复制代码
[NSMachPort port](source1)
使runloop不退出,实际并无给这个port发消息仿照 QuartzCore/UIKit
框架的模式,实现了一套相似的界面更新的机制:即在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting
和 kCFRunLoopExit
事件,在收到回调时,遍历全部以前放入队列的待处理的任务,而后一一执行。
dispatch_semaphore_t 是一个信号量机制,信号量到达、或者 超时会继续向下进行,不然等待,若是超时则返回的结果一定不为0,信号量到达结果为0。GCD信号量-dispatch_semaphore_t
经过监听mainRunloop的状态和信号量阻塞线程的特色来检测卡顿,经过kCFRunLoopBeforeSource
和kCFRunLoopBeforeWaiting
的间隔时长超过自定义阀值则记录堆栈信息。
推荐文章: RunLoop实战:实时卡顿监控
建立CADisplayLink对象的时候会指定一个selector,把建立的CADisplayLink对象加入runloop,因此就实现了以屏幕刷新的频率调用某个方法。
在调用的方法中计算执行的次数,用次数除以时间,就算出了FPS。
注:iOS正常刷新率为每秒60次。
@implementation ViewController {
UILabel *_fpsLbe;
CADisplayLink *_link;
NSTimeInterval _lastTime;
float _fps;
}
- (void)startMonitoring {
if (_link) {
[_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_link invalidate];
_link = nil;
}
_link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsDisplayLinkAction:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)fpsDisplayLinkAction:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
self.count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
_fps = _count / delta;
NSLog(@"count = %d, delta = %f,_lastTime = %f, _fps = %.0f",_count, delta, _lastTime, _fps);
self.count = 0;
_fpsLbe.text = [NSString stringWithFormat:@"FPS:%.0f",_fps];
}
复制代码
NSSetUncaughtExceptionHandler(&HandleException);
监听异常信号SIGILL
,SIGTRAP
,SIGABRT
,SIGBUS
,SIGSEGV
,SIGFPE
回调方法内建立一个Runloop,将主线程的全部Runmode都拿过来跑,做为应用程序主Runloop的替代。
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFArrayRef allModesRef = CFRunLoopCopyAllModes(runloop);
while (captor.needKeepAlive) {
for (NSString *mode in (__bridge NSArray *)allModesRef) {
if ([mode isEqualToString:(NSString *)kCFRunLoopCommonModes]) {
continue;
}
CFStringRef modeRef = (__bridge CFStringRef)mode;
CFRunLoopRunInMode(modeRef, keepAliveReloadRenderingInterval, false);
}
}
复制代码
能够把本身建立的线程添加到Runloop中,作一些频繁处理的任务,例如:检测网络状态,定时上传一些信息等。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- (void)run
{
NSLog(@"----------run----%@", [NSThread currentThread]);
@autoreleasepool{
/*若是不加这句,会发现runloop建立出来就挂了,由于runloop若是没有CFRunLoopSourceRef事件源输入或者定时器,就会立马消亡。 下面的方法给runloop添加一个NSport,就是添加一个事件源,也能够添加一个定时器,或者observer,让runloop不会挂掉*/
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 方法1 ,2,3实现的效果相同,让runloop无限期运行下去
[[NSRunLoop currentRunLoop] run];
}
// 方法2
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// 方法3
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
NSLog(@"---------");
}
- (void)test
{
NSLog(@"----------test----%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
复制代码
以上均为我的对runloop的资料收集及部分理解,若有错误请指正,欢迎讨论。