集思广益iOS学习iOS 开发每天分享优质文章

RunLoop详解

2016-10-08  本文已影响94人  YY_Lee

什么是RunLoop?
从字面意思看叫做运行循环,俗称“跑圈”。

RunLoop的主要作用:
1、保持程序的持续运行
2、处理APP中的各种事件(触摸事件,定时器事件,selector事件)
3、节省CPU资源,提高程序性能:有事做事,没事休息

main函数中的RunLoop:

如果没有RunLoop:


屏幕快照 2016-10-05 下午10.40.43.png

没有RunLoop,程序执行第三行后就结束了;

如果有RunLoop:

屏幕快照 2016-10-05 下午10.46.53.png

RunLoop内部其实就是一个do-while循环,在这个循环内部不断的处理各种事件,程序不会执行第7行代码,因此保证了程序不会退出一直运行;

屏幕快照 2016-10-05 下午10.26.44.png

UIApplicationMain函数内部自动启动了一个RunLoop,所以UIApplicationMain一直没有返回,保持程序持续运行。这个默认启动的RunLoop是跟主线程相关联的;

RunLoop对象:

iOS中有2套API访问和使用RunLoop:
Foundation框架下的:NSRunLoop
CoreFoundation框架下的:CFRunLoopRef;
CFRunLoopRef和NSRunLoop都代表RunLoop对象;NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层的API;
可参考苹果官方文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
苹果开源文档:
http://opensource.apple.com/source/CF/CF-1151.16/

RunLoop与线程

每条线程都有唯一一个与之对应的RunLoop对象;
主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动去创建;
RunLoop在第一次获取时创建,线程销毁时销毁;

获取RunLoop对象
Foundation:

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

Core Foundation:

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

RunLoop相关类
CoreFoundation中关于RunLoop的5个类:
CFRunLoopRef //RunLoop对象
CFRunLoopModeRef //RunLoop运行模式
CFRunLoopSourceRef //RunLoop事件源
CFRunLoopTimerRef //RunLoop定时器事件
CFRunLoopObserverRef //监听RunLoop状态的观察者

CFRunLoopModeRef(模式)


mode.png

一个RunLoop包含若干个Mode,每个Mode又包含若干个source、timer、observer;

每次RunLoop启动时,只能指定其中一个Mode,这个Mode称作CurrentMode;

如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入;这样做是为了分隔开不同组的source、timer、observer,互不影响;具体怎么退出,怎么重新指定Mode,官方文档并未讲述,我们也不得而知;

系统默认注册了5个Mode:

KCFRunLoopDefaultMode:APP的默认Mode,通常主线程在这个Mode下运行;

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

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

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

KCFRunloopCommonModes:这是一个占位用的Mode,不是真正的Mode;表示具有KCFRunloopCommonModes标记的模式;
KCFRunloopCommonModes标记的Mode有两种:
0 : <CFString 0x109af3a40 [0x108c687b0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x108c88b40 [0x108c687b0]>{contents = "kCFRunLoopDefaultMode"}

因此当一个事件是在RunLoop的KCFRunloopCommonModes模式下,RunLoop在KCFRunLoopDefaultMode和UITrackingRunLoopMode模式时都能处理该事件;

CFRunLoopSourceRef(事件源)
事件源的分类
从前:
1、Port-Based Sources
2、Custom Input Sources
3、Cocoa Perform Selector Sources

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

CFRunLoopTimerRef(基于时间的触发器)
NSTimer,会受到RunLoop的Mode影响
GCD的定时器不受RunLoop的Mode影响

//1.创建NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];

//2.添加到runloop,当runloop的运行模式为NSDefaultRunLoopMode时候,定时器工作
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];

那么timer只在NSDefaultRunLoopMode下工作;同理如果选择UITrackingRunLoopMode,timer只在UITrackingRunLoopMode下工作;选择NSRunLoopCommonModes,timer在UITrackingRunLoopMode和NSDefaultRunLoopMode下都能正常工作;

//0.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

//创建一个GCD定时器
/*
 第一个参数:表明创建的是一个定时器
 第四个参数:队列
 */
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

self.timer = timer;
//2.设置定时器的开始时间,间隔时间,精准度
/*
  第1个参数:要给哪个定时器设置
  第2个参数:开始时间
  第3个参数:间隔时间
  第4个参数:精准度 一般为0 提高程序的性能
 GCD的单位是纳秒
 */
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 10 * NSEC_PER_SEC);

//3.设置定时器要调用的方法
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"GCD-timer");
});
//4.启动
 dispatch_resume(timer);

定时器对象需要声明属性或成员变量去接收,否则这段代码执行完,timer会被销毁;

CFRunLoopObserverRef(观察者)
监听的时间点:

activity.png
//创建一个监听对象
/*
 第一个参数:分配存储空间的
 第二个参数:要监听的状态 kCFRunLoopAllActivities 所有状态
 第三个参数:是否要持续监听
 第四个参数:优先级
 第五个参数:回调
 */
 CFRunLoopObserverRef observer =  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"runloop进入");
            break;
            
        case kCFRunLoopBeforeTimers:
            NSLog(@"runloop要去处理timer");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"runloop要去处理Sources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"runloop要睡觉了");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop醒来啦");
            break;

        case kCFRunLoopExit:
            NSLog(@"runloop退出");
            break;
        default:
            break;
    }
});
//给runloop添加监听者
/*
 第一个参数:要监听哪个runloop
 第二个参数:监听者
 第三个参数:要监听runloop在哪种运行模式下的状态
 */
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(runLoop) userInfo:nil repeats:YES];

CFRelease(observer);

以上内容是我学习过程中查询资料总结出来的,如有疏漏或错误,欢迎批评指正,也欢迎大家一起交流讨论;关于RunLoop的应用,后面有空再继续补充;

上一篇 下一篇

猜你喜欢

热点阅读