关于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.png

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

struct __CFRunLoopMode {
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
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]
 });
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容