[toc]
RunLoop概念
u=2103244534,416163775&fm=26&gp=0.gif
代码语言:javascript复制func loop() {
repeat {
var signal = getNextMessage()
process_message(signal)
}while signal != quit
}
- RunLoop是iOS、OSX开始中非常基础的概念,也是操作系统中非常重要的一环;
- 实际上是一个对象,该对象负责事件、消息的接受和处理;
- 关键点在于:无事件避免资源占用,有事件立即响应。
mach_msg(mach消息转发机制)
系统内核在收发事件、消息时使用的消息传递函数。可以理解为多进程之间的一种通讯调用机制。
代码语言:javascript复制extern mach_msg_return_t mach_msg(
mach_msg_header_t *msg, //消息头
mach_msg_option_t option, //消息方式(发送、接受)
mach_msg_size_t send_size,
mach_msg_size_t rcv_size, //缓冲区域大小,做计算使用
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout, //该消息的超时时间,超过这个事件runloop就会进入下次loop或者sleep
mach_port_name_t notify); //消息完成后的其他通讯名称
typedef struct{
mach_msg_bits_t msgh_bits; //标示位
mach_msg_size_t msgh_size; //大小
mach_port_t msgh_remote_port; //目标端口
mach_port_t msgh_local_port; //源端口
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
unsigned int 无符号的Int,可以修饰char、int signed int 有符号的Int,我们常用的Int就是这个类型
线程和RunLoop的关系
代码语言:javascript复制CFRunLoopGetMain(void) //获取主线程的runloop
CFRunLoopGetCurrent(void) //获取当前线程的runloop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
//如果是当前线程是空的,则获取主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//线程锁,防止多个线程读取全局静态字典__CFRunLoops
__CFLock(&loopsLock);
//如果全局字典为空,在线程创建后就是空值,也就是说线程创建后默认没有runloop
if (!__CFRunLoops) {
//创建一个新的临时字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//获取主线程的Runloop,并存到字典中
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//将临时字典和全局字典做了一次copy操作,并释放临时字典。由于copy操作是临时字典的引用计数 1
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}CFRelease(mainLoop);}
//通过全局字典获取当前线程的runloop
CFRunLoopRef newLoop = NULL;
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//若不存在则创建,并写入全局字典中
if (!loop) {
newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
//2次赋值增加了对应的引用计数
if (newLoop) { CFRelease(newLoop); }
if (pthread_equal(t, pthread_self())) {
//把runloop写入当前线程的私有数据中
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
//在线程私有对象中注册一个回调,当线程销毁时将runloop也销毁了
//__CFFinalizeRunLoop是runloop的析构函数
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
RunLoop原理
一种在当前线程,持续调度各种任务的运行机制
u=2103244534,416163775&fm=26&gp=0.gif
RunLoop原型
代码语言:javascript复制while (alive) {
performTask()
callout_to_observer()
sleep()
}
事件处理
每次运行都会执行若干个task,执行task的方式有很多:
- DoBlocks() 这种方式可以被开发者使用
- DoSources0() 可对外使用
- DoSources1() 只能供系统使用
- DoTimers() NSTimer相关
- DoMainQueue() 开发者调用 GCD 的 API 将任务放入到 main queue 中
对外通讯
代码语言:javascript复制static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity)
对外通知统一使用此方法,根据参数可以看到只有:kCFRunLoopBeforeTimers,kCFRunLoopBeforeSources几个方式,其他的应该是无法准确控制任务执行时长。
睡眠
代码语言:javascript复制__CFRunLoopServiceMachPort(::::)
睡眠后有4种情况可以唤醒runloop:
- 基于port的source事件
- timer事件
- runloop超时
- 外部手动触发唤醒
事实上runloop的执行时很复杂的,会交叉进行,并不是看到的这样简单.
runloop在一次loop中可能会做的事
代码语言:javascript复制while (alive) {
//执行任务
DoBlocks();
DoMainQueue();
DoObservers-Sources();
DoSources0();
DoSources1();
DoObservers-Timer();
DoTimers();
//通知外部
DoObservers-Waiting();
//休眠
sleep()
}
//可以通过代码验证一下:
let obs = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0) { (observer, activity) in
print("Status has changed into: (activity)")
}
CFRunLoopAddObserver(CFRunLoopGetCurrent(), obs, CFRunLoopMode.defaultMode)
Source/Timer/Observer 被称为modeItem
mode是RunLoop的运行模式,modeItem是模式中的因子(事件本身)
代码语言:javascript复制struct __CFRunLoop {
__CFPort _wakeUpPort;//内核向该端口发送消息可以唤醒runloop
CFMutableSetRef _commonModes;//Set存储的是字符串,记录所有标记为common的mode,标识符
CFMutableSetRef _commonModeItems; //Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode;//当前运行的mode
CFMutableSetRef _modes;// Set
...
};
而一次loop对应的对象就是mode,把loop的相关信息都定义成一个结构体
代码语言:javascript复制struct __CFRunLoopMode {
...
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
...
};
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行;nstimer
- UITrackingRunLoopMode:界面跟踪Mode,用于滚动视图追踪触摸滑动,保证界面滑动时不受其他 Mode影响;
- UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode;
- GSEventReceiveRunLoopMode:接受系统事件的内部Mode;
- kCFRunLoopCommonModes:这是一个占位用的Mode,并不是一种真正的Mode;
commonModes
CommonModes是一个标识符,并不是一个具体的Mode。每当RunLoop的内容发生变化时,RunLoop都会自动将commonModeItems里的Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
RunLoop运行
代码语言:javascript复制void CFRunLoopRun(void) {
int32_t result;
do {
//默认在default的modl
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
//runloop的核心方法
代码语言:javascript复制//CFRunLoopRef 当前runloop
//CFRunLoopModeRef 当前mode
//seconds 超时唤醒时间
//stopAfterHandle 处理后是否停止
//previousMode 上一次loop的modl
static int32_t __CFRunLoopRun(::::){
//runloop、mode停止,返回停止状态
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//主端口
__CFPort dispatchPort = CFPORT_NULL;
...
//mode端口
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
...
//唤醒runloop
//在当前线程下创建计时器
//在没有任何msg消息的情况下根据超时时间,超时后唤醒
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置时间上下线文
dispatch_set_context(timeout_timer, timeout_context);
//时间函数的回调,用来激活runloop
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
//时间函数的取消回调
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
//只执行一次
//获取当前mode的端口
__CFPortSet waitSet = rlm->_portSet;
//通知Observers,runloop将触发 Before Timer && Source
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行加入的block
__CFRunLoopDoBlocks(rl, rlm);
//执行Sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行由Sources0分配来的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息。
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
//获取到内核的消息
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
goto handle_msg;
}}
//通知Observers,runloop即将进入sleep
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//runloop进入睡眠
__CFRunLoopSetSleeping(rl);
do{
//通过调用mach_msg,等待接受machPort的消息,但并不处理。线程将进入休眠,直到被下面某一个事件唤醒,或者该runloop被销毁
//__CFRunLoopServiceMachPort相同方法参数不同,分别表示查询到立刻返回和一直等待有消息再返回;将对应port赋值给liveport
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
}while{}
//唤醒且开始接受事件
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
//接收到某个时间被唤醒runloop被唤醒了
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//处理mach_msg消息成功的标签
handle_msg:;
//如果一个Timer到时间了,触发这个Timer的回调, 且重新布置下一次计时器
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
//主端口
else if (livePort == dispatchPort) {
//通过线程的私有方法里添加gcdMain
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
//如果有dispatch到main_queue的block,执行block。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
//剩余port表示只剩source1,则获取对应source1,或没获取到则抛出异常
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
//执行对应source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
//创建且销毁source1的对应mach_msg
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
//执行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
//根据参数处理完事件返回
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
//超出入参的超时时间
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
//runloop被停止
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
//mode被停止
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
//runloop中没有mode可执行
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
/// 如果没超时,mode里没空,loop也没被停止,那继续loop。
do『...} while (retVal == 0);
//释放timer
应用:
- 实现线程保活(AFNetworking)
- AutoreleasePool
- 事件响应
- 手势识别
- GCD
- NSTimer
- PerformSelecter
- 界面刷新
参考:
https://blog.ibireme.com/2015/05/18/runloop/ https://opensource.apple.com/tarballs/ https://github.com/apple/swift-corelibs-foundation/(Swift 开源后,苹果又维护了一个跨平台的 CoreFoundation 版本)
后知后觉的几个点: 1.gcd的定时器并不是基于runtime,它是高于runtime,runtime是基于gcd定时器 2.Commonmode是defaultmode和trackMode的集合!一般情况下被commone就是同时放入这两个mode的itemmode中 3,runloop是存在__CFRunLoops,而__CFRunLoops是一个全局的字典,和runloop本身无关。以线程名为KEY。