RunLoop可以理解为是苹果让线程保持运行,并能够随时处理事件的一个循环。它能够让线程在没有任务处理时休眠避免资源浪费,在有消息的时候被唤醒来处理事件。线程和RunLoop是一一对应的关系,在分线程中Runloop默认是不开启的,需要我们开启之后才会产生一个Runloop对象。我下载了CoreFoundation的源码可以从CFRunLoop.c文件中验证
//这个是存储 Runloop 对象的一个全局字典变量
static CFMutableDictionaryRef __CFRunLoops = NULL;
//操作 loopsLock 的锁
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
//__CFRunLoops 如果这个全局变量不存在则创建一个对象并赋予__CFRunLoops 顺便创建主线程的mainLoop
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
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);
__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) {
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);
}
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;
}
在源码中看到了这几个与RunLoop有关的类
typedef CFStringRef CFRunLoopMode CF_EXTENSIBLE_STRING_ENUM;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
CFRunLoopRef : RunLoop对象
CFRunLoopModeRef : RunLoop的Model
CFRunLoopSourceRef : RunLoop的事件,可以分为两类source0 和 source1。source0的触发需要先把source0标记未待处理事件,然后再手动唤醒RunLoop。source1则可以主动唤醒RunLoop。
CFRunLoopTimerRef:时间触发器,NSTimer可以看做是这个类的封装
CFRunLoopObserverRef :RunLoop的观察者类,当RunLoop的状态发生改变的时候会有回调产生
我在一个分线程里面直接输出了当前的RunLoop
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
NSLog(@"分线程 %@",runLoop);
});
可以看到一个Model里面存在sources0,sources1,observers,timers。当然由于当前线程RunLoop的Model中所有内容全部为null,RunLoop会直接退出,不进入循环。
结合上面的控制台输出,以及看的源码,RunLoop中的内容大概是下面这个图。
struct __CFRunLoopMode {
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
Model不可以自己创建只可以使用苹果给出的Model类型
NSDefalutRunLoopMode 默认Model
UITrackingRunLoopMode 滑动时的Model
UIInitializationRunLoopMode 这个是私有的,在App启动出现的Model
NSRunLoopCommonModes 默认包括上面第一和第二
当我们在主线程的中创建一个定时器如果是放在 Defalut Mode中,当App处于普通状态的时候,这个定时器会运行并回调,但是当我们在滑动App中存在的Scrollview时,RunLoop会切换到UITracking Mode中,这时候定时器的回调不会继续。如果我们想定时器在任何时候都可以产生回掉,则可以把定时器放在Common Modes中,或者分别放在Defalut Mode和UITracking Mode中。
下面说说几个运用RunLoop实现的功能。
1:AutoreleasePool
在主线程RunLoop即将进入的时候,会创建自动释放池,然后再每次即将休眠之前去释放旧的释放池,然后再创建一个新自动释放池,最后在RunLoop即将推出的时候去释放自动释放池。
2:PerformSelecter
这个方法调用的时候会在当前的RunLoop中注册一个Timer事件,如果是 performSelector:onThread:这个方法会在对应的线程里面注册一个Timer。当然这些前提都是需要当前线程有对应的RunLoop,并且RunLoop所运行的Model里面的item不是空的,不然RunLoop会直接退出,再去注册相应的Timer也无从谈起了。
3:分线程的保活
其实就是在分线程中获取对应的RunLoop并且在Model中添加一些item,让分线程中的RunLoop不会直接退出,因为RunLoop的存在也会让线程不至于死亡,可以继续执行任务。
比较经典的例子就是AFNetworking 2.x版本里面使用用到的
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
4:界面更新:当一个UIView或者CALayer需要被更新的时候,这个UIView/CALayer就会被标记,在RunLoop即将进入休眠的一个回掉中去更新这个UIView/CALayer
5:NSTimer
当我们在一个分线程里面去创建一个NStimer的时候
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(action) userInfo:nil repeats:YES];
});
然后你会发现这个定时器并没有起作用,它的回调不会走。这个就是上面说过的RunLoop对象不存在,我们需要获取RunLoop对象并让其跑起来,那么这个定时器就会在RunLoop上面注册Timer并产生回调。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(action) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run]
});
如果我们是这个语句创建的 定时器则需要在再把定时器手动添加到RunLoop中去
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run]
});