RunLoop是什么?基本操作是什么?

1、RunLoop的作用

RunLoop可以:

  • 保持程序的持续运行

  • 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)

  • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

学到这里,你就知道了RUnLoop的作用了吧。看看程序里的例子:

程序中的main函数里面:

int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

UIApplicationMain里面就开启了一个RunLoop,这个默认启动的RunLoop是跟主线程相关联的。它就可以处理我们上面说的那些事情,说白了就是让CUP有时间休息,没事的时候帮我们省电。

下面我们看看怎么访问它:

2、iOS中有2套API来访问和使用RunLoop

1.Foundation

NSRunLoop

2.Core Foundation

CFRunLoopRef

2.1、两者的关系:

NSRunLoop和CFRunLoopRef都代表着RunLoop对象

NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

2.2、如何获得RunLoop对象

Foundation

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

Core Foundation

CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

3、RunLoop和线程的关系

每条线程都有唯一的一个与之对应的RunLoop对象

主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建

RunLoop在第一次获取时创建,在线程结束时销毁

4、RunLoop的结构

如图所示:

一个RunLoop包含若干个Mode,

而每个Mode又包含若干个Source、Timer、Observer

对应的类是:

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

每个RunLoop启动时,只能指定一种Model,并且切换Mode时,只能先退出RunLoop,这样是为了分隔开不同组的Source、Timer、Observer。

RunLoop有5种Mode:

系统默认注册了5个Mode:

NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行,可以把这个理解为一个”过滤器“,我们可以只对自己关心的事件进行监视。

UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

NSRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

5、RunLoop的内部类

每个Mode又包含若干个Source、Timer、Observer,他们对应的类如下:

5.1、CFRunLoopTimerRef
  • CFRunLoopTimerRef是基于时间的触发器

  • CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响

  • GCD的定时器不受RunLoop的Mode影响

5.2、CFRunLoopSourceRef
  • CFRunLoopSourceRef是事件源(输入源)

  • 按照官方文档,Source的分类

    • Port-Based Sources
    • Custom Input Sources
    • Cocoa Perform Selector Sources
  • 按照函数调用栈,Source的分类

    • Source0:非基于Port的, 用于用户主动触发事件
    • Source1:基于Port的,通过内核和其他线程相互发送消息
5.3、CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

可以监听的时间点有以下几个

  • 添加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);

RunLoop的使用

下来是Run Loop的使用场合:

  1. 使用port或是自定义的input source来和其他线程进行通信
  2. 在线程(非主线程)中使用timer
  3. 使用 performSelector…系列(如performSelectorOnThread, …)
  4. 使用线程执行周期性工作
  • run loop不需要创建,在线程中只需要调用[NSRunLoop currentRunLoop]就可以得到
  • 假设我们想要等待某个异步方法的回调。比如connection。如果我们的线程中没有启动run loop,是不会有效果的(因为线程已经运行完毕,正常退出了)。

  • 你不需要在任何情况下都去启动一个线程的 run loop。比 如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动 run loop。Run loop 在你要和线程有更多的交互时才需要,比如以下情况:

 使用端口或自定义输入源来和其他线程通信

 使用线程的定时器

 Cocoa 中使用任何 performSelector...的方法

 使线程周期性工作

如果你决定在程序中使用 run loop,那么它的配置和启动都很简单。和所有线程 编程一样,你需要计划好在辅助线程退出线程的情形。让线程自然退出往往比强制关闭它更好。关于更多介绍如何配置和退出一个 run loop,参阅”使用 Run Loop 对象” 的介绍。

终于学好了关于RunLoop的基本概念,

我们知道了,RunLoop接收到两种事件就会去调用相应的方法处理

事件,两种事件分别是输入源(input source)和定时源 (timer source),换句话说,RunLoop就是所有要监视的输入源和定时源以及要通知的 run loop 注册观察 者的集合。

所以,我们要知道

Run loop 入口

Run loop 何时处理一个定时器

Run loop 何时处理一个输入源

Run loop 何时进入睡眠状态

Run loop 何时被唤醒,但在唤醒之前要处理的事件

Run loop 终止

例子

给子线程添加RunLoop

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(show) object:nil];
[thread start]; - (void)show
{
[NSRunLoop currentRunLoop]; // 只要调用currentRunLoop方法, 系统就会自动创建一个RunLoop, 添加到当前线程中
}

常驻线程

有这么一个需求,我们要在子线程中没接收一个事件就调用一次方法。但是子线程在完成任务后就销毁,全局变量强引用?试试

//
// ViewController.m
// NSThreadTest
//
// Created by 薛银亮 on 14/8/10.
// Copyright (c) 2014年 薛银亮. All rights reserved.
// #import "ViewController.h" @interface ViewController ()
@property (nonatomic, strong)NSThread *thread;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:@"xyl"];
self.thread = thread;
[thread start];
} -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:@"xyl" waitUntilDone:YES];
} -(void)run{
NSLog(@"runrunrunrun");
}
-(void)test{
NSLog(@"testtesttest");
}
@end

结果令人感到遗憾:线程只能执行一个函数run,然后就死亡了。就算用全局的变量引用着,这个线程也只是存在于内存中,同样是死亡状态,不能持续的执行。

  • 想在子线程中不断执行任务,必须保证子线不处于死亡状态

  • 但是子线程执行完一次任务就进入死亡状态

  • 那我们可以把线程停留在进入死亡状态之前,这里可以用RunLoop

    • 我们可以在线程初始化的时候执行的方法中给他创建一个运行时RunLoop,这是他就可以不断接收source,也就是这样
-(void)run{
NSLog(@"runrunrunrun");
[[NSRunLoop currentRunLoop]addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop]run];
}
  • 注意RunLoop:

启动前内部必须要有至少一个item,虽然Obsever也是item的一种,但是只会等待Timer和Source ,Timer是因为有回调,Source是会接收事件,所以当RunLoop里面有Timer或者Source的时候,RunLoop会等待里面的item(除Observer以外)主动给他发消息,然后Observer被动的接收RunLoop发送过来的消息,亦即是说,能主动给RunLoop发消息的item会让RunLoop跑起来并且不退出。

    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

     //1.将NSTimer添加在Default模式, 定时器只会运行在Default Mode下, 当拖拽时Mode切换为Tracking模式所以没反应
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 2.将NSTimer添加在Tracking模式, , 定时器只会运行在Tracking Mode下,当停止时Mode切换为Default模式所以没反应
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 3.将NSTimer添加为被标记为Common的模式, Default和Tracking都被标记为了Common, 所以都有反应
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; // 4.scheduled创建的定时器默认添加在Default模式, 所以不用手动添加, 但是后期也可以修改
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 修改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

注意:GCD的定时器不受RunLoop的影响,因为RunLoop底层是使用GCD实现timer的

  • GCD定时器

    有这么一个需求,需要这么一个定时器,误差几乎为0的定时器,但是无论是NSTimer还是CGDisplayLink都会有误差,而且误差都比较大,这是我们可以用GCD来实现定时器,实际上,上面已经说了,RunLoop底层也是调用GCD的source来实现NSTimer的,只是NSTimer还受mode的影响,下面来看看怎么用GCD实现
//    获取队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 创建定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置定时器属性(什么时候开始,间隔多大)
// 定义开始时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
// 定义时间间隔
uint64_t interver = (uint64_t)(1.0 * NSEC_PER_SEC);
// 设置开始时间和时间间隔
dispatch_source_set_timer(self.timer, start,interver, 0);
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"==================") ;
});
// dispatch_cancel(self.timer);
// self.timer = nil;
// 取消定时器
// 启动定时器
dispatch_resume(self.timer);

线程除了处理输入源,Run Loops也会生成关于Run Loop行为的通知(notification)。Run Loop观察者(Run-Loop Observers)可以收到这些通知,并在线程上面使用他们来作额为的处理,我们可以像下面这样添加一个观察者给RunLoop

添加RunLoop监听

// 创建Observer
// 第一个参数:用于分配该observer对象的内存
// 第二个参数:用以设置该observer所要关注的的事件
// 第三个参数:用于标识该observer是在第一次进入run loop时执行, 还是每次进入run loop处理时均执行
// 第四个参数:用于设置该observer的优先级
// 第五个参数: observer监听到事件时的回调block
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch(activity)
{
case kCFRunLoopEntry:
NSLog(@"即将进入loop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出loop");
break;
default:
break;
}
});

将上面的监听添加到观察者


/*
第一个参数: 给哪个RunLoop添加监听
第二个参数: 需要添加的Observer对象
第三个参数: 在哪种模式下监听
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode); // 释放observer
CFRelease(observer);

RunLoop面试题

  • 什么是RunLoop?

    • 从字面意思看:运行循环、跑圈

      其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)

    • 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法)

    • RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop

    • 自动释放池什么时候释放?

    • 通过Observer监听RunLoop的状态

  • 在开发中如何使用RunLoop?什么应用场景?

    • 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)

    • 在子线程中开启一个定时器

    • 在子线程中进行一些长期监控

    • 可以控制定时器在特定模式下执行

    • 可以让某些事件(行为、任务)在特定模式下执行

    • 可以添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情)

iOS-RunLoop,为手机省电,节省CPU资源,程序离不开的机制的更多相关文章

  1. iOS RunLoop详解

    1. RunLoop简介 1.1 什么是RUnLoop 可以理解为字面的意思:Run表示运行,Loop表示循环.结合在一起就是运行的循环.通常叫做运行循环. RunLoop实际上是一个对象,这个对象在 ...

  2. iOS Runloop 消息循环

    介绍 Runloop是一种事件监听循环,可以理解成一个while死循环,监听到事件就起来,没有就休息. Runloop可以在不同模式下进行切换,iOS有五种模式,其中UIInitializationR ...

  3. iOS runLoop 原理多线程 总结 NSTimer优化

    可以理解为字面意思:Run 表示运行,Loop 表示循环.结合在一起就是运行的循环的意思.哈哈,我更愿意翻译为『跑圈』.直观理解就像是不停的跑圈. RunLoop 实际上是一个对象,这个对象在循环中用 ...

  4. iOS RunLoop简介

    一.什么是RunLoop? RunLoop是运行循环,每个Cocoa应用程序都由一个处于阻塞状态的do/while循环驱动,当有事件发生时,就把事件分派给合适的监听器,如此反复直到循环停止.处理分派的 ...

  5. ios -RunLoop(简单理解)

    一. RunLoop简介 RunLoop字面意思是运行时,即跑圈得意思.它可以在我们需要的时候自己跑起来运行,在我们没有操作的时候就停下来休息,充分节省CPU资源,提高程序性能. 二. RunLoop ...

  6. ios runloop学习

    今天突然才之间才意识到NSTimer这样的运行方式,是在多线程中实现的循环还是在主线程中去实现的呢.当然不可能是在主线程中的while那么简单,那样什么都干不了,简单看了下NSTimer是以同步方式运 ...

  7. magnitude是精确距离,sqrMagnitude是节省CPU的粗略距离,精度有损失

    magnitude是精确距离,sqrMagnitude是节省CPU的粗略距离,精度有损失 CubeA坐标 x:0 y:0.844 z:0 CubeC坐标 x:0 y:0 z:0 Vector3 aaa ...

  8. 解决ios下部分手机在input设置为readonly属性时,依然显示光标

    解决ios下部分手机在input设置为readonly属性时,依然显示光标 在出现如上所说的问题是尝试给input 加上  onfocus="this.blur()"  方法 添加 ...

  9. 一个表缺失索引发的CPU资源瓶颈案例

    背景 近几日,公司的应用团队反应业务系统突然变慢了,之前是一直比较正常.后与业务部门沟通了解详情,得知最近生意比较好,同时也在做大的促销活动,使得业务数据处理的量出现较大的增长,最终系统在处理时出现瓶 ...

随机推荐

  1. 64位ubuntu下装32位软件

    本帖最后由 wuy069 于 2013-10-25 12:28 编辑 很多软件只有32位的,有的依赖32位库还挺严重的:从ubuntu 13.10已经废弃了ia32-libs,但可以使用多架构,安装软 ...

  2. pjsip视频通信开发(上层应用)之EditText重写

    我们经常使用手机的打电话功能,当我们按键盘的时候,有一个地方显示我们按键的内容,当我们的手点击那个地方的时候,并没有弹出软件盘,所以我们再有数字键盘的时候,要屏蔽系统的软件盘. 我们分析一下,软件盘弹 ...

  3. iis7负载均衡

    Windows平台分布式架构实践 - 负载均衡(下)   Windows平台分布式架构实践 - 负载均衡   Windows平台分布式架构实践 - 负载均衡   概述 最近.NET的世界开始闹腾了,微 ...

  4. XCode7,打包上传的一些警告,及参考处理方法

    1.ERROR ITMS-90046 /90085: "Invalid Code Signing Entitlements. Your application bundle's signat ...

  5. Android典型界面设计——ViewPage+Fragment实现区域顶部tab滑动切换

    一.问题描写叙述 本系列将结合案例应用,陆续向大家介绍一些Android典型界面的设计,首先说说tab导航,导航分为一层和两层(底部区块+区域内头部导航).主要实现方案有RadioGroup+View ...

  6. MySQL 5.7.12新增MySQL Shell命令行功能

      在最新发布的MySQL 5.7.12中有许多令人兴奋的新功能,对于MySQL开发者来说,最令人兴奋的莫不是新增的MySQL Shell了,其下载地址: http://dev.mysql.com/d ...

  7. window.location.Reload()和window.location.href 区别

    首先介绍两个方法的语法: reload 方法,该方法强迫浏览器刷新当前页面.语法:location.reload([bForceGet])参数: bForceGet, 可选参数, 默认为 false, ...

  8. 入门之--linux配置php

    [下载php源码]:从php的官方网站下载php的安装源码包. [解压安装包]:下载的安装包是经过压缩的gz格式,在linux可以使用自带的工具tar进行解压,在安装包所在目录执行命令:tar -zx ...

  9. mysql二进制包安装与配置实战记录

    导读 一般中小型网站的开发都选择 MySQL 作为网站数据库,由于其社区版的性能卓越,搭配 PHP .Linux和 Apache 可组成良好的开发环境,经过多年的web技术发展,在业内被广泛使用的一种 ...

  10. 关于h5手机端上拉加载和下拉刷新效果-1

    1.手机端目前很火的效果,上拉加载,和下拉刷新.目前主要使用 iscroll 框架来实现.先推荐一个iscroll中文学习的网站,不要感谢,我是雷锋. 2.https://iiunknown.gitb ...