iOS RunLoop了解和使用

RunLoop

上次讲了runtime,这次是runloop,虽然两者都是run开头的名词术语,但是在OC中,这两个东西压根没啥联系。这篇文章主要讲讲runloop的一些概念和用法。其中包含:

  • 什么runloop
  • runloop是怎么存在的
  • runloop中包含哪些东西
  • 日常开发中使用到runloop 的场景

一、什么是runloop

一个很容易想到的现象: 当我们将手机解锁进入某个APP之后,如果不操作手机(包括网络请求的行为),手机不会有任何反应,一旦我们进行了操作的时候,手机就会执行响应的动作。那当我们不操作的时候手机在做什么呢?  如果对受的状态进行检测的话,会发现CPU的使用率几乎是零,也就是说,没有进行操作的时候的手机的状态是休眠的! 专业看来说,在手机没有接受事件的时候,手机自动进入休眠状态,但是保持一种随时可唤醒的姿势,等待用户的事件传入。这种机制在很多不同的系统中都有,被称之为事件循环。在苹果中,实现这一机制的就是RunLoop了。

我们大概把runloop想象成一个do-while循环,在不接受退出的指令的状态,会一直执行循环下去。而runloop在这基础之上加入了更复杂的逻辑,使得runloop不仅可以在没有事件输入的情况下进行休眠,同时做到随时随地的响应事件。

逻辑上可以理解成下面的代码。

func loop() {
    do {
        var message = get_next_message();
        process_message(message);   //处理信息
    } while (message != quit);
}

当然,实际上比这个要复杂的多。至少我不觉的这个循环里我能做一些精确的控制。让我们先看看runloop中还包含了其他的一些的东西。

先看看NSRunLoop。NSRunLoop是基于CF RunLoopRef的封装,但两者并不是 toll-free briged,在OC中,可以使用下面的方式获取。

[aRunLoop  getCFRunLoop];

看看苹果对NSRunloop的介绍说明:

The NSRunLoop class declares the programmatic interface to objects that manage input sources. An NSRunLoop object processes input for sources such as mouse and keyboard events from the window system, NSPortobjects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.

NSRunLoop管理程序的输入事件,这些事件包含了来自窗口鼠标和键盘的事件,以及NSport端口事件和网络请求事件,同时还包括了timer事件。也即所有的事件,都会经过runloop的管理,在APP中有序的执行。

二、runloop是怎么存在的

在苹果的NSRunloop 的文件中我们看到这些:

FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;
FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes

@property (class, readonly, strong) NSRunLoop *currentRunLoop;
@property (class, readonly, strong) NSRunLoop *mainRunLoop NS_AVAILABLE(10_5, 2_0);
#endif

@property (nullable, readonly, copy) NSRunLoopMode currentMode;

- (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;

- (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
- (void)removePort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;

- (nullable NSDate *)limitDateForMode:(NSRunLoopMode)mode;
- (void)acceptInputForMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

@end

@interface NSRunLoop (NSRunLoopConveniences)

- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
- (void)configureAsServer NS_DEPRECATED(10_0, 10_5, 2_0, 2_0);
#endif

/// Schedules the execution of a block on the target run loop in given modes.
/// - parameter: modes   An array of input modes for which the block may be executed.
/// - parameter: block   The block to execute
- (void)performInModes:(NSArray<NSRunLoopMode> *)modes block:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/// Schedules the execution of a block on the target run loop.
/// - parameter: block   The block to execute
- (void)performBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

这其中,我发现它并不能通过某个方式创建。正常的Object对象,一般都可以通过 init 来创建实力对象。然而,NSRunLoop并没有。但是我们可以看到NSRunLoop有一个属性:

@property (class, readonly, strong) NSRunLoop *currentRunLoop;

事实上,NSRunLoop不能直接创建,只能通过currentRunLoop来获取。并且每个RunLoop只能在当前线程中操作,因为runloop不是线程安全的。看过喵神的技术博客的应该知道,NSRunLoop是基于CFRunloop的封装,而CFRunloop是纯C的API,它是线程安全的。这里一直提到了线程,其实是想说明,NSRunLoop依赖于线程,并且每个线程最多只有一个NSRunLoop,对于主线程,在APP启动时候,main函数中就已经开启了一个NSRunLoop用于服务主线程,这个runloop可以通过  NSRunLoop 的 mainRunLoop获得。如果是CFRunLoop ,则可以通过CFRunLoopGetMain() 和 CFRunLoopGetCurrent()获取当前线程的runloop和主线程的runloop。

线程和runloop一一对应,当线程被销毁的时候,runloop也GG了。所以,要维系一个runloop,首先要保证线程的存状态。其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

三、runloop中包含哪些东西

runloop管理了线程中的事件,这些事件包含:timer事件、source事件以及Observer。还包含了事件的运行的model-RunLoopMode。

timer事件即是我们使用的定时器。

source事件包含两类,一类是source0,另一类是source1,两者的区别是:

? Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
? Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

Observer是观察者,它可以监听runloop的运行状态,可以监听以下状态:

kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop

kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer

kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source

kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒

kCFRunLoopExit          = (1UL << 7), // 即将退出Loop

RunLoopMode:这是runloop的一种标记。它有几种形式:

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

上面提到的timer、source、Observer都不是直接放在runloop中的,而是作为item的形式被标记为某个model之中,然后runloop运行在对应的model时,才会管理这些item。当我们要处理某个item的时候,首先要确保item处于runloop对应的mode之中。

四、日常开发中使用到runloop 的场景

runloop在日常的开发中,使用的场景是比较少的。我们经常会有这么一种情况,如果使用定时器执行的一个小动画,在拖动scrollView的时候会被暂停。这是因为,runloop通常情况在kCFRunLoopDefaultMode中运行,也即说,直接创建的NStimer将在这个model中。在拖动scrollView的时候,runloop就切换到了UITrackingRunLoopMode下,之前在kCFRunLoopDefaultMode中的item不再被runloop维护,也就停止了运行。要决解这个,只需要通过

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;

将NStimer加入到UITrackingRunLoopMode中即可。 还有一种办法,在所有的mode中,还有一种kCFRunLoopCommonModes,它是的作用类似于它的名字,时公共的mode,也即是在这个mode中的item,会被所有的mode支持。 我们可以将NStimer标记为Commonmode达到同样的目的。

还有其他的类似的情况之后,我们还能在著名的AFNetWorking中看到使用了Runloop :

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
 

这样开启了一个线程,同时开启runloop,并添加了一个port事件维系runloop 的运行,但是port并不发送时机的消息。这个线程是AFNetWorking用于将NSURLConection置于后台处理请求和回调的。

Y神的文章:http://blog.ibireme.com/2015/05/18/runloop/

时间: 2024-08-08 22:07:32

iOS RunLoop了解和使用的相关文章

iOS runloop详解

写这篇文章开始之前,我都不知道runloop是什么东西,如果从字面的意思翻译应该是一直循环的跑,怀疑可能和死锁有关系,可是死锁具体是怎么回事,我只是记得有这个说法,也发现了一个自己不懂的知识. 初识runloop 我在网上看了一下@sunnnyxx 关于runloop的视频.了解了一下runloop相关知识,也去网络上看各种关于runloop的讲述. 我们一般程序就是执行一个线程,是一条直线,有起点终点,而runloop就是一直在线程上面画圆圈,一直在跑圈,除非切断否则一直在运行.网上说的比喻很

iOS runloop 资源汇总-b

RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池.延迟回调.触摸事件.屏幕刷新等功能的. 1.先来一发,孙源大神的讲解视频:http://v.youku.com/v_show/id_XODgxODkzODI0.html(亮点:讲解通俗易懂,理解深刻) 2.孙源大神原文博客:http://www.code4app.

ios Runloop

一.概念:一个Runloop就是一个事件处理的循环,用来不停的调度工作和处理输入事件,使用runloop的目的是让你的线程在有工作的时候处于工作状态,没有工作的时候处于休眠状态. 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出.如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的: 1 2 3 4 5 6 7 function loop() {     initialize();     do {         var message = get_nex

iOS Runloop理解

一.RunLoop的定义 当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程.RunLoop就是控制线程生命周期并接收事件进行处理的机制. RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统. Foundation: NSRunLoopCore Foundation: CFRunLoop 核心部分,代码开源,C 语言编写,跨平台 二.目的 通过RunLoop机制实现省电,流畅,响应速度快,用户体验好 三.理解 进程是一家工厂,线程是一个流水线,Run Loo

iOS Runloop学习笔记

一.* what is run loop * 1.A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc). Each NSThread has its own run loop, which can be accessed via t

IOS RunLoop浅析 一

RunLoop犹如其名循环. RunLoop 中有多重模式. 在一个"时刻"只能值执行一种模式. 因此在使用RunLoop时要注意所实现的效果有可能不是你想要的. 在这里用NSTimer展示一下Runloop的简单实现. 在故事板中添加一个TextView(用于测试) 我们吧nstimer加入到NSDefaultRunLoopMode模式中 在上面我们可以很清晰的看到,当我们滚动TextView的时候,nstimer不在执行. // // ViewController.m // CX

IOS - RunLoop消息循环

什么是RunLoop? -RunLoop就是消息循环,每一个线程内部都有一个消息循环. -只有主线程的消息循环默认开启,子线程的消息循环默认不开启. RunLoop的目的 -保证程序不退出 . -负责处理输入事件.  -如果没有事件发生,会让程序进入休眠状态  . 事件类型 Input Sources (输入源) & Timer Sources (定时源) -输入源可以是键盘鼠标,NSPort, NSConnection 等对象,定时源是NSTimer 事件 添加消息到循环中 -创建输入源.(以

IOS RunLoop浅析 三

经过两篇的介绍我想对RunLoop应该有了简单的了解,至少不至于一无所知. 在这篇我想对"CFRunLoopObserverRef"做一下简单的补充. 在补充之前先说一下. 在现在的开发中已经很少见到MRC了. 但是那是对与OC对象的. CFRunLoopObserverRef属于CF (Core Foundation) 所以我们需要手动释放. 规则如下: 凡是带有creat copy retain 等成分的函数创建出来的对象都要要在最后进行释放,即Release. // // Vie

IOS RunLoop浅析 二

上一篇我们说了runloop 的几种模式,那么我们在模式中又要做些什么呢??? 模式中有三个模块: 事件源(输入源) Source Source: 按照官方文档分类 Port-Based Custom Input Cocoa Perform Selector 按照函数调用栈,Source的分类 Source0:非基于Port的 Source1:基于Port的,通过内核和其他线程通信,接受,分发系统事件. (这里没什么太大用,剩下的Source概念我就不介绍了有兴趣可以去别处查查) 观察者 Obs