iOS-RunLoop,为手机省电,节省CPU资源,程序离不开的机制
2015-08-10 23:52
369 查看
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.FoundationNSRunLoop
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的使用场合:
使用port或是自定义的input source来和其他线程进行通信在线程(非主线程)中使用timer
使用 performSelector…系列(如performSelectorOnThread, …)
使用线程执行周期性工作
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 - - JSON 和 XML解析
- iOS多线程中performSelector: 和dispatch_time的不同
- iostransitiontranslate闪屏问题总结
- iOS设置tabBarItem.image图片渲染模式
- iOS 自动布局
- iOS 越狱机免证书调试
- iOS 手势识别器
- iOS之TableView分组目录(快速索引)的使用
- IOS中的沙盒机制
- ios开发中的官方地图(CLLocationManager)使用
- ios测试的时候出现错误
- IOS开发之通知NSNotificationCenter
- iOS9 Beta5内置壁纸—艺术家的文艺范
- IOS MapKit 输入城市名获取经纬度显示地图
- PhoneGap入门 iOS 插件开发
- IOS(数据持久化1)
- IOS-lazyload思想
- ios测试框架的理解
- IOS--JSON数据解析成字典
- iOS多线程(Grand Central Dispatch)