总资料
全是随笔 笔记 与 学习资料。没有规律。
苹果的闭源代码包括NSFoundation
的GNUStep的重新实现,有一定的参考价值。
GNUStep
: http://www.gnustep.org/resources/downloads.php
一、RunLoop 相关类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
typedef struct __CFRunLoop * CFRunLoopRef;
//全部结构
/*
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
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;
};
*/
//精简后 关键点:
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
Runloop
的相关状态:
/* 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
};
二、CFRunLoopModeRef
CFRunLoopModeRef
代表RunLoop
的运行模式- 一个
RunLoop
包含若干个Mode, 每个Mode 又包含__CFRunLoopMode
的里面的内容 RunLoop
启动时只能选择其中一个mode, 作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
- 不同组的Mode能够分开来,互不影响
- 如果当前Mode里面没有任何
_sources0/_sources1/_observers/_timers
, RunLoop会立刻退出。
常见的两种Mode
KCFRunLoop1DefaultMode
(NSDefaultRunLoopMode
): App的默认Mode,通常主线程就是运行的这个ModeUITrackingRunLoopMode
:界面跟踪Mode, 用于ScrollView
追踪触摸滑动,保证界面滑动不受其他Mode影响
三、RunLoop 运行逻辑与步骤
四、RunLoop 核心代码
这里为删除了大部分 不重要的代码,只保留了最主要的 逻辑代码。
轮询来处理消息
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
{
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
//通知observer 即将处理timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理block
__CFRunLoopDoBlocks(rl, rlm);
//处理source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//再次处理block
__CFRunLoopDoBlocks(rl, rlm);
}
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
//判断是否有source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//直接跳转到处理消息的地方
goto handle_msg;
}
}
//通知observer 即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//休眠
__CFRunLoopSetSleeping(rl);
do {
msg = (mach_msg_header_t *)msg_buffer;
//等待其他消息 来唤醒当前线程。 阻塞中。。。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
//取消睡觉,已被消息唤醒
__CFRunLoopUnsetSleeping(rl);
//通知observer 休眠结束
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
//被timer 唤醒
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//gcd 唤醒
else if (livePort == dispatchPort) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
//source 唤醒 __CFRunLoopDoSource1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
//处理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;
}
__CFRunLoopServiceMachPort
实现真正休眠的代码为:
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|
(voucherState ? MACH_RCV_VOUCHER : 0)|
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);
mach_msg
这个函数在用户态和内核中都有实现的。也就是说这个函数从用户态到内核态的转变。真正休眠的代码是由内核态完成的,真正实现了让CPU和线程假死状态,不处理任何事情。
MACH_RCV_MSG
代表是接收端, 能够接受其他进程或者线程的 信息,当其他线程往这个端口发信息的时候,runloop就会被唤醒,处理信息中需要做的事情。 所以 mach port 可以用来作为最基础的进程间通信。
五、实际应用
- 控制线程的生命周期,线程保活
- NSTimer滑动停止工作
- 监控应用卡顿
- 性能优化
线程保活
- 线程和RunLoop为1对1的关系,线程内部本身是没有RunLoop的,当线程内部第一次调用RunLoop时,会自动创建RunLoop
- RunLoop 保证线程永远活着。
1.封装的类:NSRunloop
#import "MyThreadObject.h"
@interface MyThreadObject()
@property (nonatomic, strong) NSThread *innerThread;
@property (nonatomic, assign) BOOL isStopped;
@end
@implementation MyThreadObject
#pragma mark - public
- (instancetype)init {
if (self = [super init]) {
__weak typeof(self) weakSelf = self;
self.innerThread = [[NSThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
while (weakSelf.innerThread && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
}
return self;
}
//开始线程
- (void)run {
if (self.isStopped) return;
[self.innerThread start];
}
//结束线程
- (void)stop {
if (self.isStopped) return;
[self performSelector:@selector(_stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
//运行block
- (void)execTask:(void (^)(void))task {
if (self.isStopped) return;
[self performSelector:@selector(_execTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
#pragma mark - private
- (void)_execTask:(void (^)(void))task {
task();
}
- (void)_stop {
self.isStopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
@end
2.封装的类:CFRunloop (推荐,不需要while 循环调用)
#import "MyThreadObject.h"
@interface MyThreadObject()
@property (nonatomic, strong) NSThread *innerThread;
@end
@implementation MyThreadObject
#pragma mark - public
- (instancetype)init {
if (self = [super init]) {
self.innerThread = [[NSThread alloc] initWithBlock:^{
CFRunLoopSourceContext context = {
0}; //初始化为0
CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
CFRelease(sourceRef);
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false /* 事件处理完成后不返回,等待runloop stop后才返回 */);
}];
}
return self;
}
//开始线程
- (void)run {
if (!self.innerThread) return;
[self.innerThread start];
}
//结束线程
- (void)stop {
if (!self.innerThread) return;
[self performSelector:@selector(_stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
//运行block
- (void)execTask:(void (^)(void))task {
if (!self.innerThread) return;
[self performSelector:@selector(_execTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
#pragma mark - private
- (void)_execTask:(void (^)(void))task {
task();
}
- (void)_stop {
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
@end
六、Runloo 响应用户操作
touchesBegan:withEvent
的调用堆栈
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102d1de35 140_RunLoop_iOS`-[ViewController touchesBegan:withEvent:](self=0x00007ff21300e3b0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002914d80) at ViewController.m:75:6
frame #1: 0x00007fff25031c7f UIKitCore`forwardTouchMethod + 321
frame #2: 0x00007fff250426c4 UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
frame #3: 0x00007fff250449df UIKitCore`-[UIWindow sendEvent:] + 5295
frame #4: 0x00007fff2501b4e8 UIKitCore`-[UIApplication sendEvent:] + 825
frame #5: 0x00007fff250b128a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 8695
frame #6: 0x00007fff250b3a10 UIKitCore`__processEventQueue + 8579
frame #7: 0x00007fff250aa1b6 UIKitCore`__eventFetcherSourceCallback + 240
frame #8: 0x00007fff20369e25 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x00007fff20369d1d CoreFoundation`__CFRunLoopDoSource0 + 180
frame #10: 0x00007fff203691f2 CoreFoundation`__CFRunLoopDoSources0 + 242
frame #11: 0x00007fff20363951 CoreFoundation`__CFRunLoopRun + 875
frame #12: 0x00007fff20363103 CoreFoundation`CFRunLoopRunSpecific + 567
frame #13: 0x00007fff2c851cd3 GraphicsServices`GSEventRunModal + 139
frame #14: 0x00007fff24ffbe63 UIKitCore`-[UIApplication _run] + 928
frame #15: 0x00007fff25000a53 UIKitCore`UIApplicationMain + 101
frame #16: 0x0000000102d1e8fe 140_RunLoop_iOS`main(argc=1, argv=0x00007ff7bd1e4c68) at main.m:17:12
frame #17: 0x0000000102f2ae1e dyld_sim`start_sim + 10
frame #18: 0x000000010d1d24fe dyld`start + 462
- 首先 source1 捕获用户事件(
TODO:堆栈中并没有看到source1)*后续: 首先是由那个Source1 接收IOHIDEvent,之后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。所以UIButton事件看到是在 Source0 内的。你可以在 __IOHIDEventSystemClientQueueCallback 处下一个 Symbolic Breakpoint 看一下。*所以堆栈内没有source1,因为它是上一个循环的产物。 - 然后 source0 处理事件
- source0 和sourc1都是被动的,source1监听端口
七、线程保活的意义
- 主线程 不需要添加任何 source 就能保活, 能够让程序不退出
- 子线程保活,能够创建常住线程
- 子线程常驻线程 能够监听主线程的卡顿
八、其他
performSelecter:afterDelay:
和performSelector:onThread:
都是在runloop里面的timer 驱动的,所以该线程没有runloop就会调用失败- runloop 中 mach port 很重要。source1 和 runloop的休眠监听都是通过mach port 实现的。
- runloop 出了被事件唤醒外,还有可能被自己设置的time 超时唤醒。
九、多线程
https://editor.csdn.net/md?articleId=122094507
备注:部分笔记包含有MJ老师的学习资料。