RunLoop

一,概念

RunLoop是通过内部维护的\color{#FF3366}{事件循环}来对\color{#FF3366}{事件、消息进行管理}的一个\color{#FF3366}{对象}

事件循环是什么?

事件循环重要通过2点:

  • 没有消息需要处理时,休眠以避免资源占用
    用户态 ----> 内核态
    当没有消息要处理时,进程会进入休眠状态,把当前线程的控制权交给了内核态

  • 有消息需要处理时,立刻被唤醒
    用户态 <---- 内核态
    科普:内核态和用户态

在我们的程序中 通过
int main(int argc, char * argv[]) {
@autoreleasepool {
\color{#FF3366}{return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
}
}
来实现 整个程序的runloop
runloop具备的特点:

1,不断地接收消息
2,对事件进行处理
3,继续等待

\color{#FF3366}{RunLoop是状态的切换,并不是简单的DO WHILE循环}

二,数据结构

Apple为我们提供了两个RunLoop
----\color{#999999}{NSRunLoop}----\color{#999999}{CFRunLoop}----
NSRunLoop是CFRunLoop的封装,提供了面向对象的API
NSRunLoop位于Foundation当中
CFRunLoop位于CoreFoundation当中

CFRunLoop数据结构
  • CFRunLoop
  • CFRunLoopMode
  • Source/Timer/Observer
CFRunLoopDataMode.png
CFRunLoop
  • Pthread: C级别的线程对象,RunLoop和线程是一一对应的
  • currentMode: RunLoop当前所处于的模式
  • Modes: 多个mode的集合
  • commonModes: 是一个字符串的集合
  • commonModeItems: 也是一个集合,包含了多个Observer/Timer/Source
CFRunLoopMode
CFRunLoopMode.png
  • name: 对应的是某个RunLoopMode的名称,比如RunLoop.defualt,实际上是别名的字符串
  • sources0和sources1是个集合类型的数据结构
  • observers和timers是一个数组
Source
  • source0: 需要手动唤醒线程

  • source1: 具备唤醒线程的能力

Timer
Observer
  • 我们可以通过注册一些ObServer来实现对RunLoop一些时间点的监测或观察

\color{#3399FF}{那我们可以监测哪些时间点呢?}

观测时间点

  • \color{#B0171F}{kCFRunLoopEntry:} 当Runloop准备启动的时候,会有个kCFRunLoopEntry的回调通知
    *\color{#B0171F}{kCFRunLoopBeforeTimers: } 通知观察者Runloop将要对timer事件进行处理
  • \color{#B0171F}{kCFRunLoopBeforeSources: } 通知观察者Runloop将要处理一些Source事件
  • \color{#B0171F}{kCFRunLoopBeforeWatig: } 通知观察者Runloop将要进入休眠状态 用户态->内核态
  • \color{#B0171F}{kCFRunLoopAfterWatig: } 从内核态切换到用户态不久时发生
  • \color{#B0171F}{kCFRunLoopExit: } 当前RunLoop退出

各个数据结构之间的关系

  • 线程和RunLoop是一一对应的
  • RunLoop可以有n个mode
  • 每一个mode都可以有m个Source,Timer,Observer
ModeRelationship.png

当RunLoop运行在某个mode上面的时候,如果另一个mode当中的timer事件或者Observer事件发生了回调,那么RunLoop是无法接收到的
这也就是RunLoop当中有多个mode的原因,实际上是起到了屏蔽的作用。
这也就是为什么,我们在滑动TableView的时候,我们的轮播图会暂停播放的原因

CommonMode的特殊性

*\color{#FF0000}{CommonMode其实不是一个实际存在的MODE}

  • 实际上,它是同步Source/Timer/Observer到多个Mode中的一种\color{#FF0000}{技术解决方案}

三,事件循环的实现机制

RunLoop流程.png

在RunLoop启动之后,会发送一个通知,告诉观察者即将进入RunLoop,之后RunLoop会将要处理Timer/Source0事件,之后正式进入Source0事件处理,如果有Source1需要处理,系统会通过一条goto语句来处理唤醒时收到的消息,反之线程就会将要休眠,此时发生用户态转换成核心态,再之后系统就会正式休眠,等待唤醒,而等待唤醒的条件有三个:Source1进行唤醒,timer事件发生,或者是外部手动唤醒,当系统被唤醒后,会给观察者再发生一个通知,循环到即将进入RunLoop,当程序被杀死的时候,才会即将退出RunLoop。

RunLoop的核心

RunLoop核心.png

当main函数进行处理后,系统会调用mach_msg()函数,于是就发生了系统调用,把控制权交给核心态,然后mach_msg()在一定条件下(Source1,timer,手动唤醒),会把控制权交给用户态,当前app的主运行循环会被唤醒

RunLoopObserver与Autorelease Pool的关系

RunLoopAndAutoReleasePool.jpg

UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。

RunLoop的机制

主线程 (有 RunLoop 的线程) 几乎所有函数都从以下六个之一的函数调起:

  • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    CFRunloop is calling out to an abserver callback function
    用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
    CFRunloop is calling out to a block
    消息通知、非延迟的perform、dispatch调用、block回调、KVO

  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    CFRunloop is servicing the main desipatch queue

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    CFRunloop is calling out to a timer callback function
    延迟的perform, 延迟dispatch调用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
    CFRunloop is calling out to a source 0 perform function
    处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    CFRunloop is calling out to a source 1 perform function
    由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort

四,RunLoop和NSTimer

  • NSTimer怎样保证参数的生命周期

NSTimer可以选择是否重复执行,为了保证NSTimer调用的方法中传递的对象生命周期,NSTimer会对外界传递的对象进行一次retain。
如果是一次性调用的NSTimer,会在本次调用完毕之后invalidate掉NSTimer自身,而NSTimer做retain的对象也会被进行一次release。但是如果是多次重复调用的NSTimer,就需要我们自己在某个特定的时刻来invalidate掉NSTimer,这个invalidate的时刻是根据我们代码情况来自己决定的,否则将会一直存在。
下面的方法我们先创建了一个Object对象,然后添加了一个NSTimer(关于NSTimer和Runloop后面再讲),并且进行了一次release,这时Object并没有被释放,而是被NSTimer进行了一次retain,我们通过在Object的dealloc方法中打印就可以知道是否被释放。
在本次NSTimer的Timer所调用方法调用完毕之后,NSTimer会invalidate自身,而Object对象也会被释放。

Object *object = [[Object alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:object selector:@selector(timerAction:) userInfo:nil repeats:NO]; [object release];

而通过下面这种方式创建的Timer就不会被NSTimer自动释放,因为这次调用是重复调用,必须我们显示的进行invalidate,NSTimer才会消失,这时Object对象也就会释放了。

Object *object = [[Object alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:object selector:@selector(timerAction:) userInfo:nil repeats:YES]; [object release];

我们前面做演示的代码创建的NSTimer会默认为我们添加到Runloop的NSDefaultRunLoopMode中,而且由于是在主线程中,所以Runloop是开启的,不需要我们手动打开。
在我们进行多线程编程时,所有的Source都需要添加到Runloop中才能生效,对于我们的NSTimer当然也需要添加到Runloop中才能生效。如果一个Runloop中没有任何Source的话,会立即退出的。而主线程的Runloop在程序运行时,系统就已经为我们添加了很多Source到Runloop中,所以主线程的Runloop是一直存在的,我们可以通过打印MainThread中的Runloop来查看所包含的Source。
下面的代码就没有添加到Runloop中,所以这个NSTimer永远也不会发生作用,这是一份错误的代码示例。

` Object *object = [[Object alloc] init];

NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:object selector:@selector(timerAction:) userInfo:nil repeats:NO];
[object release]; `

在iOS多线程中,每一个线程都有一个Runloop,但是只有主线程的Runloop默认是打开的,其他子线程也就是我们创建的线程的Runloop默认是关闭的,需要我们手动运行。
我们可以通过[NSRunLoop currentRunLoop]来获得当前线程的Runloop,并且调用[runloop addTimer:timer forMode:NSDefaultRunLoopMode]方法将定时器添加到Runloop中,最后一定不要忘记调用Runloop的run方法将当前Runloop开启,否则NSTimer永远也不会运行。

五,补充:AFNetworking中RunLoop的创建

`+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
     // 这里主要是监听某个 port,目的是让这个 Thread 不会回收
    [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;

}`

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

推荐阅读更多精彩内容

  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,209评论 0 5
  • 前言 RunLoop是iOS和OSX开发中非常基础的一个概念,这篇文章将从CFRunLoop的源码入手,介绍Run...
    暮年古稀ZC阅读 2,169评论 1 19
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技术 RunLoop 是 iOS 和 ...
    橙娃阅读 821评论 1 2
  • 转载:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling阅读 1,418评论 0 13
  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 594评论 0 2