Runloop核心机制和原理

原文地址

Runloop核心机制和原理

搞iOS之后一直没有深入研究过RunLoop,非常的惭愧。刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研究了RunLoop的原理和特性。

RunLoop的定义

当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。

RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。

Foundation: NSRunLoop

Core Foundation: CFRunLoop 核心部分,代码开源,C 语言编写,跨平台

目的

通过RunLoop机制实现省电,流畅,响应速度快,用户体验好

理解

进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。

RunLoop管理流水线,流水线才不会因为无所事事被工厂销毁;而不需要流水线时,就会辞退RunLoop这个主管,即退出线程,把所有资源释放。

RunLoop并不是iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都需要类似RunLoop的循环机制实现,Android的Looper就是类似的机制。

特性

主线程的RunLoop在应用启动的时候就会自动创建

其他线程则需要在该线程下自己启动

不能自己创建RunLoop

RunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoop

RunLoop负责管理autorelease pools

RunLoop负责处理消息事件,即输入源事件和计时器事件

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 架构

RunLoop 运行时

主要有以下六种状态:

kCFRunLoopEntry -- 进入runloop循环

kCFRunLoopBeforeTimers -- 处理定时调用前回调

kCFRunLoopBeforeSources -- 处理input sources的事件

kCFRunLoopBeforeWaiting -- runloop睡眠前调用

kCFRunLoopAfterWaiting -- runloop唤醒后调用

kCFRunLoopExit -- 退出runloop

RunLoop 运行时调用栈

主线程App运行时

RunLoopObserver与Autorelease Pool的关系

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

RunLoop的挂起与唤醒

指定用于唤醒的mach_port端口

调用mach_msg监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在mach_msg_trap状态。

由另一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工作。

RunLoop支持的消息事件(Events)

RunLoop

支持接收处理输入源(Input Source)事件,包括:

系统的Mach Port事件,是一种通讯事件

自定义输入事件

支持接受处理定时源(Timer)事件

在启动RunLoop之前,必须添加监听的输入源事件或者定时源事件,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。

如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。

没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。

//错误做法NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];while(!self.isCancelled && !self.isFinished) {    [runLoop runUntilDate:[NSDatedateWithTimeIntervalSinceNow:3]];};//正确做法NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];[runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];while(!self.isCancelled && !self.isFinished) {@autoreleasepool{        [runLoop runUntilDate:[NSDatedateWithTimeIntervalSinceNow:3]];    }}

Run Loop Modes

理解

Run Loop Mode就是流水线上支持生产的产品类型,流水线在一个时刻只能在一种模式下运行,生产某一类型的产品。消息事件就是订单。

Cocoa定义了四中Mode

Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下

Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件

Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件

Event tracking:UITrackingRunLoopMode,拖动事件

Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式

RunLoop可以通过[acceptInputForMode:beforeDate:]和[runMode:beforeDate:]来指定在一段时间内的运行模式。如果不指定的话,RunLoop默认会运行在Default下(不断重复调用runMode:NSDefaultRunLoopMode beforDate:)

在主线程启动一个计时器Timer,然后拖动UITableView或者UIScrollView,计时器不执行。这是因为,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而创建的Timer是默认关联为Default Mode,因此系统不会立即执行Default Mode下接收的事件。解决方法:

NSTimer* timer = [NSTimerscheduledTimerWithTimeInterval:1.0target:selfselector:@selector(timerFireMethod:)                                                userInfo:nilrepeats:YES];                                                  [[NSRunLoopmainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];//或[[NSRunLoopcurrentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];[timer fire];

Run Loop应用实践

Run Loop主要有以下三个应用场景:

维护线程的生命周期,让线程不自动退出,isFinished为Yes时退出。

NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];[runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];while(!self.isCancelled && !self.isFinished) {@autoreleasepool{            [runLoop runUntilDate:[NSDatedateWithTimeIntervalSinceNow:3]];    }}

创建常驻线程,执行一些会一直存在的任务。该线程的生命周期跟App相同

@autoreleasepool{NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];        [runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];        [runLoop run];}

在一定时间内监听某种事件,或执行某种任务的线程

如下代码,在30分钟内,每隔30s执行onTimerFired:。这种场景一般会出现在,如我需要在应用启动之后,在一定时间内持续更新某项数据。

@autoreleasepool{NSRunLoop* runLoop = [NSRunLoopcurrentRunLoop];NSTimer* udpateTimer = [NSTimertimerWithTimeInterval:30target:selfselector:@selector(onTimerFired:)                                                  userInfo:nilrepeats:YES];   

 [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];   

 [runLoop runUntilDate:[NSDatedateWithTimeIntervalSinceNow:60*30]];

}

AFNetworking中RunLoop的创建

+ (void)networkRequestThreadEntryPoint:(id)__unused object {

@autoreleasepool{ 

 [[NSThreadcurrentThread] setName:@"AFNetworking"];

NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];

// 这里主要是监听某个 port,目的是让这个 Thread 不会回收

[runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];      

  [runLoop run];    

}

}

+ (NSThread*)networkRequestThread {

staticNSThread*_networkRequestThread=nil;staticdispatch_once_toncePredicate;dispatch_once(&oncePredicate, ^{  

_networkRequestThread =[[NSThreadalloc]initWithTarget:selfselector:@selector(networkRequestThreadEntryPoint:)                                  object:nil];      

  [_networkRequestThread start];   

 });

return_networkRequestThread;

}

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

推荐阅读更多精彩内容