RunLoop基本简介(一)

最近研究了一下RunLoop,发现这个还是我们不得不面对的一个课题,对这几天的研究做个总结

一.什么是RunLoop

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样:

/// 全局的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是转圈,然后系统一直跑圈,如果是没有事情了,那么进入睡眠状态,CPU有事做事,没事休眠,提高CPU的利用率.

RunLoop只能处于一个模式,如果要去另一模式,先去暂停,然后再去开启另一个模式,去运行另一个模式内部的Timer,Source,Observer

RunLoop的懒加载,要用引文的代码

RunLoop有默认模式

1.1 NSTimer

在项目中我们经常使用定时器,现在简单的使用一下

- (void)timer
{
   //使用这个方法创建的timer,一定要显示的添加到RunLoop中
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0
                                             target:self
                                           selector:@selector(run)
                                           userInfo:nil
                                            repeats:YES];
    // 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // 定时器只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 定时器会跑在标记为common modes的模式下
    // 标记为common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

- (void)run
{
    NSLog(@"----run");
}

demo的样式
- (void)timer2
{
    // 调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                      target:self
                                                    selector:@selector(run)
                                                    userInfo:nil
                                                     repeats:YES];
    
    // 修改模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

   //NSTimer scheduledWithTarget 默认已经添加到了当前RunLoop中的默认模式,
   //但是拖拽之后,也是会停止的,所以你可以添加一下这个问题common模式
}

tag:
1.common的时候也只是运行了一种模式,而不是两种,只是default和tracking都是common模式,静止的时候是default模式,而tracking的时候,停止了default模式中的所有东西,然后开启了tracking内部的所有东西
2.[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];这个是将timer添加到RunLoop,并且指定了mode,将来很多属性都有设置相应的mode,要去注意; 例如:如何优化UITableview的时候,我们让照片不去更新,只有在defaultMode下才去更新(下边有这个例子)
3.面试题:为什么轮播图有的时候在我们滑动tableview的时候停止了,如何修改

1.2 CFRunLoopRef
在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:


关系图

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

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1(从调用栈来分的)。source就是事件
• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

区别:source0是本线程的东西,source1是处理其他线程或者是内核传递过来的事件

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。
如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环

调用栈示意图
RunLoop的示意图

该图的示意:
1.就是跑圈,然后看看有无Port,Source0,Timer事件,如果有,执行,但是如果没有,那么就歇息停止;
2.如果有timer,那么每隔几秒,就去跑圈,看看有没有东西要去执行,如果东西,那么就去执行,没听,进入休眠,倾倒自动释放池

CFRunLoopObserverRef
整体流程图

RunLoop观察者,只观察runLoop的状态;可以观测的时间点有以下几个:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

创建一个观察者

- (void)observer
{
    // 创建observer
    /**
     1.创建的时候有两种方法,但是这个比较好,因为使用的是block回调,而另一方法使用的是指针函数
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });

    // 添加观察者:监听RunLoop的状态
    /**
     1.CFRunLoopRef fl 表示要去监听哪一个runLoop
     2.CFRunLoopObserverRef observer 监听者,手动创建
     3.CFRunLoopMode mode ,监听什么模式下的
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    // 释放Observer
    /*
     CF的内存管理(Core Foundation)
     1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
     * 比如CFRunLoopObserverCreate
     2.release函数:CFRelease(对象);
     */

    CFRelease(observer);
}
打印结果
2017-04-15 13:47:03.042 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---1
2017-04-15 13:47:03.043 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.044 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.049 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.050 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.052 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.054 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.055 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.055 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.056 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.056 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.056 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.056 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.792 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.792 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:03.793 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---32
2017-04-15 13:47:04.340 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---64
2017-04-15 13:47:04.340 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:47:04.341 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:47:04.341 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---32
2017-04-15 13:48:00.000 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---64
2017-04-15 13:48:00.009 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---2
2017-04-15 13:48:00.009 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---4
2017-04-15 13:48:00.010 04-RunLoop[3908:127518] ----监听到RunLoop状态发生改变---32

这段代码配置上面的option来看,就知道1,2,4,32,64是什么意思了,就知道了RunLoop运行的顺序了

kCFRunLoopEntry(1)进入runloop
kCFRunLoopBeforeTimers(2)
kCFRunLoopBeforeSources(4)

'''

kCFRunLoopBeforeTimers(2)
kCFRunLoopBeforeSources(4)

kCFRunLoopBeforeWaiting(32)
kCFRunLoopAfterWaiting(64)醒了,开始跑圈

kCFRunLoopBeforeTimers(2)
kCFRunLoopBeforeSources(4)

kCFRunLoopBeforeWaiting(32)进入睡眠了~

可以去拦截系统的方法,在执行系统方法之前,做点东西

如果我们添加一个定时器怎么样?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self timer];
    [self observer];
}


2017-04-15 14:02:34.850 04-RunLoop[4148:139374] ----run
2017-04-15 14:02:34.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---2
2017-04-15 14:02:34.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---4
2017-04-15 14:02:34.851 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---32
2017-04-15 14:02:36.848 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---64
2017-04-15 14:02:36.849 04-RunLoop[4148:139374] ----run
2017-04-15 14:02:36.849 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---2
2017-04-15 14:02:36.849 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---4
2017-04-15 14:02:36.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---32
2017-04-15 14:02:38.848 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---64
2017-04-15 14:02:38.849 04-RunLoop[4148:139374] ----run
2017-04-15 14:02:38.849 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---2
2017-04-15 14:02:38.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---4
2017-04-15 14:02:38.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---32
2017-04-15 14:02:40.849 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---64
2017-04-15 14:02:40.849 04-RunLoop[4148:139374] ----run
2017-04-15 14:02:40.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---2
2017-04-15 14:02:40.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---4
2017-04-15 14:02:40.850 04-RunLoop[4148:139374] ----监听到RunLoop状态发生改变---32

观察:
32和64之间的时间间隔是2s
也就是说, 32的时候,已经进入休眠时间了,然后等待2s之后,
唤醒系统,进入64,执行遍历timer的数据没,遍历有没有Source,如果有,执行,打印run~~继续这样

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

推荐阅读更多精彩内容