您的位置:首页 > 运维架构

Runloop

2016-05-21 09:01 441 查看
一.我们先来介绍runloop的基础知识

1. 什么是runloop ?

字面意思理解就是,运行循环。但是,要说的话,我认为是这样的,每一个线程都有一个一一对应的runloop,只有主线程的runloop是自动创建的,子线程runloop是不会自动创建的,需要的时候,需要我们自己去创建,那么问题来了,什么时候去创建runloop,官方文档明确写出,runloop在第一次获取的时候创建,就是说除非我们主动的创建他,否则在子线程不会存在runloop。那么这里就引出一个问题,为什么子线程不可以像主线程一样自动创建runloop,这里我们就需要弄清楚一个问题,runloop是干什么的? runloop实际上是一个对象,这个对象管理了其需要处理的事件和消息 并且提供提供了一个入口函数执行上面的eventloop的逻辑,线程执行了这个函数之后就会一直处于这个函数的内部 “接受消息->等待->处理”的循环中,知道这个循环结束(比如传入quit消息),函数返回。用我们的白话来说的话,runloop就是用来管理线程的,有了他的管理可以节省我们cup资源,但是这个只是针对,我们一直的使用的线程,比如主线程,对于其它子线程如果没有什么必要,不必须去创建一个runloop,即使线程处于睡眠还是会占用一定的系统资源。

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;

/// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);

if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}

/// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

if (!loop) {
/// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}

OSSpinLockUnLock(&loopsLock);
return loop;
}

CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}


源码可以看出,runloop存放在dictionary key是pthread_t,value是CFRunloop,获取一个线程对应的runloop是对应的流程是1.如果是第一次进入,初始化全局dic,并为主线程创建一个runloop。

2.直接从dictionary里获取,如果取不到,去创建一个。

3.注册一个回调,当线程销毁时,顺便也销毁其对应的runloop。

讲到这里有必要谈深入一下runloop内部的结构




每一个runloop对应一个thread,他们是一一对应的关系。每一个CFRunloop对应多个 CFRunLoopMode,一个CFRunloop又对应多个,CFRunloop,CFRunloopTimer,CFRunLoopObserver
每一次调用runloop主函数时,都会指定一个mode这个mode称作currentmode,如果要切换mode只能退出runloop,为什么要这么做呢?因为为了分隔开不同组的 Source/Timer/Observer,让其互不影响。


RunLoop 的 Mode
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
...
};

这里有个概念叫做"commonmodes":一个mode可以将自己标记为"common"属性(通过将其modename添加到runloop的commonmodes中)。每当runloop的内容容易发生变化时,runloop都会自动将_commonModelIteams里的source/observer/timer,同步到具有 "common"标记的所有mode里。应用场景举例,主线程的runloop里有两个预设置的mode:KCFRunLoopDefaultMode和UITrackingRunLoopMode。这两个mode都已经被标记为"common"属性。defaultmode是app平时所处的状态,trackingrunloopmode是追踪scrollview滑动的是状态。当你创建一个timer并加到,defaultmode时,timer会得到重复的回调,但此时,滑动一个tableview时,runloop会将mode切换为trackingrunloopmode,这时,timer就不会被回调,并且也不会影响到滑动的操作。


CFRunLoopSourceRef 是事件产生的地方。source有两个版本:source0和source1。source0 只包含了一个回调(函数指针)它并不能主动触发事件。使用时,你需要先调用CFRunloopSourceSignal(source),将这个source标记为待处理,然后,手动调用CFRunLoopWakeUp(runloop)来唤醒runloop,让其处理这个事件,source1包含了一个match_port和一个回调(函数指针),被用于通过内核和其它线程相互发送消息。这种source能主动唤醒runloop的线程,其原理下面会讲到。
CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。
Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: