关于Runloop的简单了解

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);
    });
关于Runloop的简单了解_第1张图片
分线程RunLoop.png

可以看到一个Model里面存在sources0,sources1,observers,timers。当然由于当前线程RunLoop的Model中所有内容全部为null,RunLoop会直接退出,不进入循环。
结合上面的控制台输出,以及看的源码,RunLoop中的内容大概是下面这个图。

struct __CFRunLoopMode {
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
关于Runloop的简单了解_第2张图片
RunLoop

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

你可能感兴趣的:(关于Runloop的简单了解)