RunLoop第一讲

2017-01-03  本文已影响30人  奋斗的郅博
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的应用实例

- (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

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

该方法的作用是在主线程中,执行制定的方法(代码块)。
参数:
@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/

上一篇 下一篇

猜你喜欢

热点阅读