参考来源 http://www.dreamingwish.com/article/ios-multithread-program-runloop-the.html
RunLoop是一个事件循环的消息处理机制(一个圈不停的转,等着”人"来给他发配任务);
RunLoop就是一个循环,用来不停的调度工作以及处理输入事件。开启一个新的线程就是为了在这个新的线程中处理事件,但是线程一直开启的话会消耗资源,使用Runloop的目的就是当线程需要工作的时候工作,不需要工作的时候处于休眠状态;但是RunLoop不是自动启动的,它需要在你的线程代码在一个合适的时候启动RunLoop并正确的响应事件。每个线程包括主线程都有自己的RunLoop object,但是只有辅助线程才需要显示的运行他的RunLoop,而主线程会在程序启动的时候自动创建并运行它的RunLoop。
RunLoop是一个循环,你的线程进入并使用它来运行响应输入事件和事件处理程序,但是你的代码需要提供实现循环的部分语句,其实就是while和for循环来运行RunLoop。在循环中,Run Loop object来运行事件处理代码,它响应接收到的事件,并启动已安装的处理程序。
NSRunloop接收的两种源是input source/timer source;
RunLoop的概念结构以及两种源。输入源传递异步消息给响应的处理程序,调用runUntilData:方法退出;
Input Sources
As described in Apple‘s Run Loops programming guide, a run loop contains two types of sources: inputs and timers. An input source is basically some kind of external signal from outside the runloop itself.
除了处理输入源,run loops也会生成关于run loop行为的通知(notifications)。 注册runloop观察者observer可以手打这些通知,并在线程上使用他们做额外处理。
它有一个基本的作用就是管理autorelease pool,autorelease pool中的对象什么时候释放,不用开发者考虑,因为runloop会一直循环检查执行;
Runloop并不是一种并发机制,因为它不并行执行任务。不过在主操作队列中,Runloop直接配合着任务的执行,它提供了让代码异步执行的一种机制。
Runloop比起操作队列或者GCD来说,更加容易使用,因为通过runloop,开发者不必处理并发中的复杂状况,就能异步能执行任务;
一个runloop总是绑定在特定的线程中。main runloop是与主线程相关的,在每一个cocoa或者cocoaTouch程序中,这个main runloop祈祷核心作用--他负责处理UI事件,计时器,以及其他内核相关事件;无论什么时候调用计时器,NSURLConnection,或者调用performseletor:with object:after delay;runloop都将在后台发挥重要作用;
- RunLoop 模式
Run Loop 的模式是所有要监视的输入源,定时源和要通知的RunLoop注册观察者的集合。每次运行RunLoop都要设置它的运行模式。在RunLoop运行过程中,只有模式相关的源才会被监视并允许他们传递事件消息。
通常在代码中,模式都是被自己指定的。Cocoa和Core Foundation定义了一个默认的和一些常用的模式,在你的代码中都是用字符串来指定这些模式。你必须添加一个或多个输入源,定时源或者观察者才能让你的RunLoop有意义。
通过指定模式还可以使RunLoop在某一阶段过滤来源于源的事件。大多数时候,RunLoop都是运行在系统默认的模式之下。但是模态模板(modal panle)运行在”modal”模式下。在这种模式下,只有和模式面板相关的源才可以和线程传递消息。
模式的区分,基于事件的源而不是事件的种类。例如,你不可以使用模式只选择鼠标或键盘按下的事件。事件的源取决于输入源的种类:基于端口的源或者自定义的源;
表中列出的是Cocoa和Core Foundation定义的标准模式,并且介绍何时使用他们。
Mode |
Name |
Description |
Default |
NSDefaultRunLoopMode(Cocoa) kCFRunLoopDefaultMode (Core Foundation) |
The default mode is the one used for most operations. Most of the time, you should use this mode to start your run loop and configure your input sources. |
Connection |
NSConnectionReplyMode(Cocoa) |
Cocoa uses this mode in conjunction with NSConnection objects to monitor replies. You should rarely need to use this mode yourself. |
Modal |
NSModalPanelRunLoopMode(Cocoa) |
Cocoa uses this mode to identify events intended for modal panels. |
Event tracking |
NSEventTrackingRunLoopMode(Cocoa) |
Cocoa uses this mode to restrict incoming events during mouse-dragging loops and other sorts of user interface tracking loops. |
Common modes |
NSRunLoopCommonModes(Cocoa) kCFRunLoopCommonModes (Core Foundation) |
This is a configurable group of commonly used modes. Associating an input source with this mode also associates it with each of the modes in the group. For Cocoa applications, this set includes the default, modal, and event tracking modes by default. Core Foundation includes just the default mode initially. You can add custom modes to the set using the CFRunLoopAddCommonMode function. |
- 输入源
输入源异步发送消息给你的线程。事件的来源取决于输入源的种类:基于端口的输入源和自定义的输入源。基于端口的输入源监听程序相应的端口。自定义的输入源监听自定义的事件源。至于Runloop则不关心事件的来源,事件都是由系统传过来的。两类输入源的区别在于如何显示:基于端口的输入源由系统生成,自定义的输入源由自己从其他线程传入;
当自己创建输入源,我们需要将其分配给Runloop的某一个模式下。模式只会在自己的事件才影响监听的源。大多数情况下,run loop运行在默认模式下,但是也可以设置使其运行在特定的模式下 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode(可以修改) beforeDate: ]]。当一个消息在当前的模式下不能被响应,那就是属于其他的源。
基于端口的输入源
Cocoa和Core Foundation内置支持使用端口相关的对象和函数来创建的基于端口的源。例如,在Cocoa里面你从来不需要直接创建输入源。你只要简单的创建端口对象,并使用 NSPort的方法把该端口添加到run loop。端口对象会自己处理创建和配置输入源。
在Core Foundation,你必须人工创建端口和它的run loop源。在两种情况下,你都可以使用端口相关的函数(CFMachPortRef,CFMessagePortRef,CFSocketRef)来创建合适的对象。
自定义输入源
为了创建自定义输入源,必须使用Core Foundation里面的CFRunLoopSourceRef类型相关的函数来创建。你可以使用回调函数来配置自定义输入源。Core Fundation会在配置源的不同地方调用回调函数,处理输入事件,在源从run loop移除的时候清理它。
除了定义在事件到达时自定义输入源的行为,你也必须定义消息传递机制。源的这部分运行在单独的线程里面,并负责在数据等待处理的时候传递数据给源并通知它处理数据。消息传递机制的定义取决于你,但最好不要过于复杂。
Cocoa 执行selector 源
除了基于端口的源,Cocoa也可以自定义输入源,允许你在任何线程执行selector。和基于端口的源一样,执行selector的源也会在目标线程上序列化,以减少在同一个线程多个方法执行时可能会出现的同步问题。不过selector的源执行完毕后会自动从runloop移除。
当你要执行的selector在其他线程执行时,其他线程必须有一个活动的runloop,比如performSelectorOnMainThread:withObject:waitUntilDone: 主线程的runloop是一定执行的。但是如果要在自己创建的线程上执行,必须显示的启动runloop,在没有启动runloop之前线程处于等待状态。
下面是NSObject中可在其他线程执行的selector。
Methods |
Description |
performSelectorOnMainThread:withObject:waitUntilDone: |
Performs the specified selector on the application’s main thread during that thread’s next run loop cycle. These methods give you the option of blocking the current thread until the selector is performed. |
performSelector:onThread:withObject:waitUntilDone: |
Performs the specified selector on any thread for which you have an NSThreadobject. These methods give you the option of blocking the current thread until the selector is performed. |
performSelector:withObject:afterDelay: |
Performs the specified selector on the current thread during the next run loop cycle and after an optional delay period. Because it waits until the next run loop cycle to perform the selector, these methods provide an automatic mini delay from the currently executing code. Multiple queued selectors are performed one after another in the order they were queued. |
cancelPreviousPerformRequestsWithTarget: |
Lets you cancel a message sent to the current thread using theperformSelector:withObject:afterDelay:orperformSelector:withObject:afterDelay:inModes:method. |
- 何时使用 RunLoop
仅当在为你的程序创建辅助线程的时候才需要显式的运行一个RunLoop(就是开启一个新的线程的时候)。程序在启动的时候就由Cocoa和carbon开启了一个主线程,同时 开启了主RunLoop循环。
RunLoop在你和线程需要交互的时候才开启,比如:
-
- 使用端口或输入源来和其他线程通讯
- 使用线程定时器
- Cocoa中使用performSelector...的方法
- 使线程周期性工作
- 使用 Run Loop 对象
Run Loop 对象提供了添加输入源、定时器和观察者以及启动RunLoop的方法。每一个线程都有唯一与之相关的RunLoop对象。在Carbon或BSD中提供的是CFRunloopRef类型的指针。
获取RunLoop对象
-
- 在Cocoa中使用NSRunloop对象的currentRunloop方法获取NSRunloop对象
- 使用CFRunloopGetCurrent函数
虽然他俩的类型不同,但是可以再NSRunloop中获取CF的对象,它里面有一个getCFRunLoop方法;两者都指向同一个runloop。
- 配置 Run Loop
在你们辅助线程运行Run Loop 之前,你必须添加一个输入源或者定时器给他,否则他就自动退出了。
除了添加源,你也可以添加一个观察者对Run Loop 的执行阶段进行观察。
当一个长时间运行的线程添加Run Loop的时候,最好添加一个输入源到Run Loop用来接受消息。虽然可以使用辅助定时器来进入Run Loop,但是一旦定时器触发后,Run Loop就退出了。虽然添加一个循环定时器可是保持长久的运行,但是这也会导致周期性的唤醒线程,这也是轮训的另一种形式。所以最好的是添加爱一个输入源,在输入源发生之前,他一直处于休眠状态。
- 启动 Run Loop
启动Run Loop只对程序的辅助线程有用,启动后必须添加一个输入源或者一个定时器,否则Run Loop自动退出。
有三种方式启动Run Loop:无条件的、设置超时时间、设置特定模式;
无条件的是最容易的启动Run Loop的方法,但也是最不好的,因为你对Run Loop的控制最少,你可以添加输入源和定时器,但是你退出它的唯一方法就是杀死它。没有任何办法让这Run Loop运行在自定义模式下。
替代无条件最好的方法就是设置超时时间,这样的话Run Loop可以再某一事件到达,或者规定时间已到的情况下关闭Run Loop。然后重启继续使用。
还可以使用特定模式,它与时间超时不互斥,但是它可以设置自己Run Loop所关心的模式,限制了传递给Run Loop的事件源的类型。