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

RunLoop源码分析、基本使用场景和常见问题

2016-12-25 13:57 676 查看
YY大神的链接

goole ASDK的Runloop超流畅UI思路

Runloop

从字面上看

循环运行 内部核心其实就是一个do while循环

跑圈 就和这个小伙子一样



BOOL running = YES;
do{
// 处理各种事件执行各种任务

}while(running)


基本作用

保持程序的持续运行

处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)

节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠

Runloop与线程

每条线程都有唯一的一个与之对应的runloop对象

主线程的runloop自动创建,子线程的runloop需要手动创建,不创建就没有

Runloop在第一次获取时创建,在线程结束时销毁

获取runloop对象

Fundation

[NSRunLoop currentRunLoop] // 获取当前线程的

[NSRunLoop mainRunLoop]// 获取主线程

core fundation

CFRunLoopGetCurrent();// 获取当前线程

CFRunLoopGetMain(); // 获取主线程

源代码分析系列,当我们调用上面的代码时,来看看源码源码下载地址

1.入口

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;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}


2.看到
_CFRunLoopGet0
这个函数没,这就是第二部,内部实现

// 全局字典,key是pthread_t value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 访问__CFRunLoops时的锁
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 如果传进来的线程为空,默认为主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// 第一次进来全局字典里面没有任何东西
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 初始化全局字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 根据主线程创建主线程RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 加入到全局字典 key main_thread value mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 和全局__CFRunLoops关联上,然后把临时创建的杀死
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 如果全局字典存在,根据线程取RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 娶不到创建一个
if (!loop) {
// 创建
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
// 再取一次
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 还是没有
if (!loop) {
// 根据线程key把刚创建的关联上
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);
}
// 注册一个回调,当线程销毁时,通知销毁RunLoop
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);
}
}
return loop;
}


大致创建逻辑就是上面注释,你不需要显示alloc,你只需要获取就行了,内部已经有创建的逻辑,没错,RunLoop对象就搞定了,下面来看看对象到底是什么结构

RunLoop相关类

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef



CFRunLoopModeRef代表RunLoop的运行模式

一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer

每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode

如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入

这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响

系统注册了五个Mode(可以打断点看调用栈)

1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程实在这个Mode

2.UITrackingRunLoopMode: 页面跟踪Mode,用于滚动追踪触摸滑动,保证页面滑动式不受其他Mode影响

NSTimer默认是加到DefaultMode里面的,当滚动式切换到这个模式,所以之前的Timer事件不再调用

3.UIInitializationRunLoopMode:在刚启动App时第一次进入就是这个Mode,完成之后不再用

4.GSEventReveiveRunLoopMode:系统事件的内部Mode,通常用不到

5.CFRunLoopCommonModes:这个是一个占位用的Mode,不是一个真正的Mode,

用来标示切换不同Mode时是否加有这个字段的Mode还是能继续接受事件

CFRunLoopSourceRef是事件源(输入源)

理论分Source:

Port-Based Sources 基于Mach port内核/其他线程事件源

Custom Input Sources 自定义基本不用

Cocoa Perform Selector Sources [Self PerformSele…]

按调用栈分Source:

Source0:非基于Port

Source1:基于Port,通过内核和其他线程通信,接受,分发系统事件

CFRunLoopTimerRef)

这个东西就是一个定时器,通过设置特定的时间间隔来给叫醒RunLoop处理事件

CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1
kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4
kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠 32
kCFRunLoopAfterWaiting = (1UL << 6),// 即将进入从休眠中醒来 64
kCFRunLoopExit = (1UL << 7),// 退出 128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式监听枚举
};


上述提到的Source/Timer/OBserver就是一个ModeItem,如果一个Mode中一个Item都没有,RunLoop会自动退出,这就是后面要提到的常驻线程创建方式,先看看RunLoop和Mode两者的结构,然后再举例子对上面的行为进行分析

struct __CFRunLoop {
CFRuntimeBase _base;
CFMutableSetRef _commonModes; // 名字Default/Tracking
CFMutableSetRef _commonModeItems;// Timer/Source/Observer
CFRunLoopModeRef _currentMode;// 当前Mode
CFMutableSetRef _modes;//一个RunLoop下有多个Mode
};

struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
CFStringRef _name; // 用来存放到RunLoop中_commonModes/_modes的字段
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;// 非port事件
CFMutableSetRef _sources1;// port事件
CFMutableArrayRef _observers;// 观察者
CFMutableArrayRef _timers;// timer
......
}


个人理解下,RunLoop结构体中有个modes用来存放所有可切换的Mode,例如Default/Tracking等,currentMode就指定当前在运作的Mode,CommandModes就是存放着无论你切换哪个Mode,你都需要同步_commonModeItems下所有事件的ModeName,你可以把Mode看做一个个的属于你自己的任务,而commentMode看做每个人都要做的公共任务,当你有公共任务的时候也就是栈顶有Commentmode的时候,你做自己的子线任务的时候,同时需要做栈顶的公共任务

实例1Timer

// 方法1
//  on the current run loop in the default mode
// 创建的时候是加到NSDefaultRunLoopMode中的
//    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 方法2
// 其实和方法一是一样的,只是展开来写罢了
NSTimer *timer = [NSTimer timerWithTimeInterval:2.3 target:self selector:@selector(run) userInfo:nil repeats:YES];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

// 为了在滚动的时候和不滚动的时候都进行调用NSTimer
// 只需要把Timer加到当前RunLoop中的CommonModes Set容器里面去就好了,无论切换什么模式,都会同步该Timer
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];


默认情况下我们创建的Timer只会在Default状态下执行,当我们滚动的时候Timer就会不调用事件,原因就是RunLoop中它默认加到了NSDefaultMode中,当滚动的时候切换到NSTrackingMode的时候,NSTimer自然就不执行了,这是因为RunLoop只会在一个Mode下运行,当切换Mode的时候需要重启Loop,而且事件不会同步,除非你把事件加到栈顶的CommonModes里面去,这样就可以在当前RunLoop下的所有模式下共享执行了

实例2Observer,可以在事件处理前进行一些拦截

//    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
//        kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1
//        kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2
//        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4
//        kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠 32
//        kCFRunLoopAfterWaiting = (1UL << 6),// 即将进入从休眠中醒来 64
//        kCFRunLoopExit = (1UL << 7),// 退出 128
//        kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式监听枚举
//    };
CFRunLoopObserverRef observe = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopExit) {
NSLog(@"即将退出runloop%lu",activity);
}
else if (activity == kCFRunLoopBeforeWaiting)
{
NSLog(@"即将进入休眠%lu",activity);

}
else if (activity == kCFRunLoopAfterWaiting)
{
NSLog(@"刚从休眠中唤醒%lu",activity);

}else if (activity == kCFRunLoopBeforeSources)
{
NSLog(@"即将处理 Source%lu",activity);

}else if (activity == kCFRunLoopBeforeTimers)
{
NSLog(@"即将处理 Timer%lu",activity);

}
else if (activity == kCFRunLoopEntry)
{
NSLog(@"即将进入Loop%lu",activity);

}

});

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observe, kCFRunLoopDefaultMode);

/*
CF的内存管理(Core Foundation)
1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
* 比如CFRunLoopObserverCreate
2.release函数:CFRelease(对象);
*/
CFRelease(observe);




实例3 AFN2x和YYKit下 的常驻线程

// YYKit常驻线程
/// Network thread entry point.
+ (void)_networkThreadMain:(id)object {
// 常规做法最外层也用autorelease包起来
@autoreleasepool {
[[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 给创建的loop添加任意一个item
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}

/// Global image request network thread, used by NSURLConnection delegate.
+ (NSThread *)_networkThread {
static NSThread *thread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
thread.qualityOfService = NSQualityOfServiceBackground;
}
[thread start];
});
return thread;
}


这里的做法是创建 一个RunLoop,但是如果没有添加任何Obserer/Timer/Source,Runloop默认你是会退出的,要进入循环必须加一个item

RunLoop整体逻辑





这个图的前提需要注意的是,需要检查ModeItem是否为空,是的话直接退出Loop

源码分析

//1.直接调用Run方法
void CFRunLoopRun(void) {   /* DOES CALLOUT */
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);

} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 通过指定Mode调用Run方法
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// 2.启动
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
//3. 根据RunloopModeName找到Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//4. 如果Mode为空里面没有任何的item直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
//5. 如果不为空 先通知Observer:即将进入Loop kCFRunLoopEntry = (1UL << 0),// 即将进入RunLoop 1
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//6. 进入核心函数进行跑圈loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//end: kCFRunLoopExit = (1UL << 7),// 退出 128
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
// 核心跑圈代码
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
__CFPortSet waitSet = rlm->_portSet;

__CFRunLoopUnsetIgnoreWakeUps(rl);
// 8.通知Observer
// kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer事件 2
// kCFRunLoopBeforeSources = (1UL << 2), // 即将处理事件源 4

if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 任务加入Block处理
__CFRunLoopDoBlocks(rl, rlm);
//9. 处理Source0事件 执行事件加入block
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

//10. 如果有source1进来,基于port的消息进来 处理Source1 goto语句跳转到下面
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 跳转
goto handle_msg;
}
}

didDispatchPortLastTime = false;
//11. 通知ObserverseRunloop即将进入休眠kCFRunLoopBeforeWaiting
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
//12. 调用mach方法等待消息的接受,线程进入休眠,当port传来source1的时候,当Timer时间到了,当Loop超时了
// 当被显示唤醒的时候
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

} while (1);
//13. 通知Runloop线程被刚刚唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//14. 处理消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);

//15. 如果Timer到了 触发Timer回调
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//16. 如果是由dispatch到main_queue的block,执行block
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

//17. 如果一个source1发出事件,处理source1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
__CFRunLoopDoBlocks(rl, rlm);

//18.处理是否继续循环
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回。
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)) {
// source/timer/observer为空一个都没了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);// 还是0,继续跑

return retVal;
}


关于Runloop的一些问题

什么是RunLoop?

内部就是一个do - While循环,在这个循环内部不断处理各种事件

(Source、Timer、Observer)

一个线程对应一个RunLoop,主线程RunLoop默认已经启动,子线程的RunLoop得手动启动

RunLoop只能选择一个模式,如果当前模式中没有Source、Timer、Obeserver直接突出

你在开发中怎么使用RunLoop?

1.AF2x.和YYKit常驻线程

在子线程中开启定时器

子线程中长期监控一些行为(扫描网络、沙盒、语音监控)

2.可以控制定时器在特定模式下执行

3.可以让某些事件在特定模式下执行(Performance…inmodes(mode参数))

4.可以添加Observer监听RunLoop的状态,比如监听点击事件处理前处理某些事情

自动释放池什么时候释放

在RunLoop睡眠之前释放(kCFRunLoopBeforeWaiting)

总结

4.什么是Runloop

Runloop他的本质就是一个do,while循环

(1)保持程序的持续运行,当有事做时做事,

(2)负责监听处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)

(3)节省CPU资源,提高程序性能,做事的时候跑起来,不做的时候休眠。

每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动获取运行,创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。当创建的loop,没有任何上面的属性,就会直接退出。

Run的时候必须制定mode,那么这里有个知识点是CommonMode,不能指定该Mode进行运行,但是可以把事件标记到该属性,那么无论切换到哪个Mode都会执行

Apple应用

1.AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,第一个 Observer 监视的事件是 Entry(即将进入Loop),执行方法去创建pool,优先级最高,第二个是BeforeWaiting(准备进入休眠) 时调用pop and push,先清除旧pool,然后创建新pool,或者再退出的时候释放自动释放池子,优先级最低

2.启动时注册,处理事件,nstimer,UI,渲染

3.网络请求框架下的线程通信

开发者应用:

1.常驻线程 处理网络请求的回调

2.Common在不同模式下也执行NSTimer

3.添加Observe属性的观察,在即将进入休眠的ASDK的原理,以下就是AS框架的Runloop应用,这里把排版,绘制等操作放到异步做,然后存储起来

在kCFRunLoopBeforeWaiting和kCFRunLoopExit两个activity的回调中将之前异步完成的工作同步到主线程中去。

ASDK goole的UI框架原理

一个自称用cell实现Runloop应用的人

__unsafe_unretained __typeof__(self) weakSelf = self;
void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[weakSelf processQueue];
};
_runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock);
CFRunLoopAddObserver(_runLoop, _runLoopObserver,  kCFRunLoopCommonModes);


结构:

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
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息