RunLoop第一讲

cmd-markdown-logo

引言

RunLoop在大家的印象中就是很神秘的感觉,很多人都不愿去触碰它。其实对于其在项目中的应用还是非常实际的和方便的。在我们项目中也有关于其的应用实例。今天,我们一起探索关于RunLoop的那些事吧!

基本作用

  • 保持程序的持续运行(比如主运行循环)。
  • 处理App中的各种事件响应(比如触摸事件、定时器事件等)。
  • 节省CPU资源,提高程序性能(也就是说该做事时做事,该休息时休息)。

在程序中存在价值和意义

就拿程序内部的main函数为例:

图一没有RunLoop.png
图二有RunLoop.png

main函数的作用和其中实现的RunLoop(主循环)

  • main函数是程序的入口
  • 保持程序的持续运行
  • 图一中Return返回的为具体数值,有返回值,程序运行到Return就结束了,这种情况表现为我们的程序启动不起来
  • 图二中Return一直没有返回值。内部实现是通过UIApplicationMain函数内部就启动了一个RunLoop。所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
  • 图二这个默认启动的RunLoop是跟主线程相关联的

RunLoop 的对象

对于RunLoop其有两套API:

  • 基于C语言的CoreFoundation框架的CFRunLoopRef对象
  • 基于CFRunLoopRef的一层OC包装的Foundation的NSRunLoop对象

RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop在第一次获取时创建,在线程结束时将会被销毁
  • 主线程的RunLoop是已经自动创建好的,子线程的RunLoop需要主动创建

RunLoop对象的获取

基于Foundation框架的:

  • [NSRunLoop currentRunLoop] //获取当前线程的RunLoop的对象
  • [NSRunLoop mainRunLoop] //获取主线程RunLoop的对象

基于CoreFoundation框架的:

  • CFRunLoopGetCurrent() //获取当前线程的RunLoop的对象
  • CFRunLoopGetMain() //获取主线程RunLoop的对象

Core Foundation中关于RunLoop相关的类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

  • 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer,即是一个RunLoop至少有一个向对应的Mode
  • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
相关类

系统默认注册了5个Mode:(前三个比较常用)

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

CFRunLoopSourceRef

CFRunLoopSourceRef是事件源(输入源)
按照官方文档的分类

  • Port-Based Sources (基于端口,跟其他线程交互,通过内核发布的消息)
  • Custom Input Sources (自定义)
  • Cocoa Perform Selector Sources (performSelector...方法)

按照函数调用栈的分类Source有两个版本:Source0 和 Source1。

  • Source0:非基于Port的
  • Source1:基于Port的

注:Source0: event事件,只含有回调,需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
Source1: 包含了一个 mach_port 和一个回调,被用于通过内核和其他线程相互发送消息,能主动唤醒 RunLoop 的线程。

CFRunLoopTimerRef

  • CFRunLoopTimerRef是基于时间的触发器
  • 基本上说的就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop的Mode影响

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

CFRunLoopObserverRef

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


观察者可以监听的状态

使用方法调用:

- (void)observer
{
    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
    });
    // 添加观察者:监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 释放Observer
    CFRelease(observer);
}

*注:CF的内存管理(Core Foundation)
1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release 。比如CFRunLoopObserverCreate
2.release函数:CFRelease(对象);

RunLoop的应用实例

  • 场景还原
    1.NSTimer(最常见RunLoop使用)
- (void)timer
{
    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)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];
}

2.关于轮播图的拖拽Mode的变换导致的问题
由于拖拽时模式由NSDefaultRunLoopMode 进入 UITrackingRunLoopMode模式发生了变化。所以我们设置模式的时候设为NSRunLoopCommonModes 模式下两种模式都可运行
3.PerformSelector

  • -(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

创建一个线程在子线程执行,aSelector代表了新创建的线程,arg是传入的参数

  • -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

该方法的作用是在主线程中,执行制定的方法(代码块)。
参数:
@selector就是,要定义我们要执行的方法。
withObject:arg定义了,我们执行方法时,传入的参数对象。类型是id。(我们可以传入任何参数)waitUntilDone:YES指定,当前线程是否要被阻塞,直到主线程将我们制定的代码块执行完。
注意:

  • 当前线程为主线程的时候,waitUntilDone:YES参数无效。
  • 该方法,没有返回值
  • 该方法主要用来用主线程来修改页面UI的状态。

3.常驻线程
应用场景:经常在后台进行耗时操作,如:监控联网状态,扫描沙盒等 不希望线程处理完事件就销毁,保持常驻状态

- (void)run
{
  //addPort:添加端口(就是source)  forMode:设置模式
   [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
  //启动RunLoop
    [[NSRunLoop currentRunLoop] run];
 /*
  //另外两种启动方式
    [NSDate distantFuture]:遥远的未来  这种写法跟上面的run是一个意思
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    不设置模式
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
  */
}

退出-退出当前线程:

[NSThread exit];

结论:虽然RunLoop很难接触到,但是项目中也是经常出现的,关于NSTimer定时器的问题,我们每次还都是与其打交道的。

注:RunLoop相关资料
苹果官方文档
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

CFRunLoopRef是开源的
http://opensource.apple.com/source/CF/CF-1151.16/

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

推荐阅读更多精彩内容

  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技术 RunLoop 是 iOS 和 ...
    橙娃阅读 849评论 1 2
  • 转自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飘金阅读 978评论 0 4
  • 前言 最近离职了,可以尽情熬夜写点总结,不用担心第二天上班爽并蛋疼着,这篇的主角 RunLoop 一座大山,涵盖的...
    zerocc2014阅读 12,373评论 13 67
  • http://www.cocoachina.com/ios/20150601/11970.html RunLoop...
    紫色冰雨阅读 834评论 0 3
  • 我的家乡,是一个小村庄,叫下河。 是的,没错,她在一条小河边。小时候,河下游村落的人总是笑着说,你们应该叫上河才对...
    心城23阅读 417评论 0 0