深入理解RunLoop ❤️

IOS底层学习-DAY-17 ❤️

官方文档:Run Loops

Runloop知识树

深入理解Runloop,看我一篇就够了

runloop中的source0和source1分别处理什么时间

 
 

runloop运行状态

 

A run loop receives events from two different types of sources. Input sources deliver asynchronous events, usually messages from another thread or from a different application. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval. Both types of source use an application-specific handler routine to process the event when it arrives.

 

Input Sources

Input sources deliver events asynchronously to your threads. The source of the event depends on the type of the input source, which is generally one of two categories. Port-based input sources monitor your application’s Mach ports. Custom input sources monitor custom sources of events. As far as your run loop is concerned, it should not matter whether an input source is port-based or custom. The system typically implements input sources of both types that you can use as is. The only difference between the two sources is how they are signaled. Port-based sources are signaled automatically by the kernel, and custom sources must be signaled manually from another thread.

When you create an input source, you assign it to one or more modes of your run loop. Modes affect which input sources are monitored at any given moment. Most of the time, you run the run loop in the default mode, but you can specify custom modes too. If an input source is not in the currently monitored mode, any events it generates are held until the run loop runs in the correct mode.

The following sections describe some of the input sources.

Port-Based Sources

Cocoa and Core Foundation provide built-in support for creating port-based input sources using port-related objects and functions. For example, in Cocoa, you never have to create an input source directly at all. You simply create a port object and use the methods of NSPort to add that port to the run loop. The port object handles the creation and configuration of the needed input source for you.

In Core Foundation, you must manually create both the port and its run loop source. In both cases, you use the functions associated with the port opaque type (CFMachPortRefCFMessagePortRef, or CFSocketRef) to create the appropriate objects.

For examples of how to set up and configure custom port-based sources, see Configuring a Port-Based Input Source.

Custom Input Sources

To create a custom input source, you must use the functions associated with the CFRunLoopSourceRef opaque type in Core Foundation. You configure a custom input source using several callback functions. Core Foundation calls these functions at different points to configure the source, handle any incoming events, and tear down the source when it is removed from the run loop.

In addition to defining the behavior of the custom source when an event arrives, you must also define the event delivery mechanism. This part of the source runs on a separate thread and is responsible for providing the input source with its data and for signaling it when that data is ready for processing. The event delivery mechanism is up to you but need not be overly complex.

For an example of how to create a custom input source, see Defining a Custom Input Source. For reference information for custom input sources, see also CFRunLoopSource Reference.

Cocoa Perform Selector Sources

When performing a selector on another thread, the target thread must have an active run loop. For threads you create, this means waiting until your code explicitly starts the run loop. Because the main thread starts its own run loop, however, you can begin issuing calls on that thread as soon as the application calls the applicationDidFinishLaunching: method of the application delegate. The run loop processes all queued perform selector calls each time through the loop, rather than processing one during each loop iteration.

Table 3-2 lists the methods defined on NSObject that can be used to perform selectors on other threads. Because these methods are declared on NSObject, you can use them from any threads where you have access to Objective-C objects, including POSIX threads. These methods do not actually create a new thread to perform the selector.

Table 3-2  Performing selectors on other threads

Methods

Description

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

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:

performSelector:onThread:withObject:waitUntilDone:modes:

Performs the specified selector on any thread for which you have an NSThread object. These methods give you the option of blocking the current thread until the selector is performed.

performSelector:withObject:afterDelay:

performSelector:withObject:afterDelay:inModes:

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:

cancelPreviousPerformRequestsWithTarget:selector:object:

Lets you cancel a message sent to the current thread using the performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method.

 

The Run Loop Sequence of Events

Each time you run it, your thread’s run loop processes pending events and generates notifications for any attached observers. The order in which it does this is very specific and is as follows:

  1. Notify observers that the run loop has been entered.

  2. Notify observers that any ready timers are about to fire.

  3. Notify observers that any input sources that are not port based are about to fire.

  4. Fire any non-port-based input sources that are ready to fire.

  5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.

  6. Notify observers that the thread is about to sleep.

  7. Put the thread to sleep until one of the following events occurs:

    • An event arrives for a port-based input source.

    • A timer fires.

    • The timeout value set for the run loop expires.

    • The run loop is explicitly woken up.

  8. Notify observers that the thread just woke up.

  9. Process the pending event.

    • If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.

    • If an input source fired, deliver the event.

    • If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.

  10. Notify observers that the run loop has exited.

Because observer notifications for timer and input sources are delivered before those events actually occur, there may be a gap between the time of the notifications and the time of the actual events. If the timing between these events is critical, you can use the sleep and awake-from-sleep notifications to help you correlate the timing between the actual events.

Because timers and other periodic events are delivered when you run the run loop, circumventing that loop disrupts the delivery of those events. The typical example of this behavior occurs whenever you implement a mouse-tracking routine by entering a loop and repeatedly requesting events from the application. Because your code is grabbing events directly, rather than letting the application dispatch those events normally, active timers would be unable to fire until after your mouse-tracking routine exited and returned control to the application.

A run loop can be explicitly woken up using the run loop object. Other events may also cause the run loop to be woken up. For example, adding another non-port-based input source wakes up the run loop so that the input source can be processed immediately, rather than waiting until some other event occurs.

 

Exiting the Run Loop

There are two ways to make a run loop exit before it has processed an event:

  • Configure the run loop to run with a timeout value.

  • Tell the run loop to stop.

Using a timeout value is certainly preferred, if you can manage it. Specifying a timeout value lets the run loop finish all of its normal processing, including delivering notifications to run loop observers, before exiting.

Stopping the run loop explicitly with the CFRunLoopStop function produces a result similar to a timeout. The run loop sends out any remaining run-loop notifications and then exits. The difference is that you can use this technique on run loops you started unconditionally.

Although removing a run loop’s input sources and timers may also cause the run loop to exit, this is not a reliable way to stop a run loop. Some system routines add input sources to a run loop to handle needed events. Because your code might not be aware of these input sources, it would be unable to remove them, which would prevent the run loop from exiting.

 

Thread Safety and Run Loop Objects

Thread safety varies depending on which API you are using to manipulate your run loop. The functions in Core Foundation are generally thread-safe and can be called from any thread. If you are performing operations that alter the configuration of the run loop, however, it is still good practice to do so from the thread that owns the run loop whenever possible.

The Cocoa NSRunLoop class is not as inherently thread safe as its Core Foundation counterpart. If you are using the NSRunLoop class to modify your run loop, you should do so only from the same thread that owns that run loop. Adding an input source or timer to a run loop belonging to a different thread could cause your code to crash or behave in an unexpected way.

 

谁用了Runloop

概览

  • NSTimer(timer触发)

  • UIEvent事件响应和手势识别(source0事件处理 & source1底层硬件触发)

  • AutoRelease(observer,runloop一次循环结束或autorelease pool满了的时候释放)

  • NSObject(NSDelayPerforming(timer),NSThreadPerformAddition(source0))

  • dispatch_get_main_queue()(serve for dispatch main)

  • scrollView滑动(mode切换保证滑动不卡顿)

  • UI界面刷新、CATransition、CAAnimation(observer) CADisplayLink(source1)

  • NSURLConnection(source0处理回调 & source1接收Socket消息) AFNetworking(内置全局线程和runloop管理网络请求)

 

 

深入看一下 Darwin 这个核心的架构:

 

XNU内核

XNU’s Not UNIX

Mac和iOS的核心

IOKit 设备驱动提供了一个面向对象(C++)的一个框架  
BSD 围绕Mach层的外环,提供了诸如进程管理、文件系统网络等功能  
Mach

被称为内环,其作为一个微内核,仅提供了诸如处理器调度IPC (进程间通信)等非常少量的基础服务

在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”

和其他架构不同, Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信

”消息”是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach 的 IPC (进程间通信) 的核心。

IPC:

Inter-Process Communication

 

进程间通信

IPC

Inter-Process Communication

共享内存模式

直接读写共享存储区的变量来交互数据,同步与互斥在并发程序设计时安排进入程序。操作系统提供这样的共享存储区及同步互斥工具。

最为快捷有效的方式之一,UNIX系统中常被使用。内存共享区的互斥要通过其它机制实现;数据的发送方不关心数据由谁接收,数据的接收方也不关心数据是由谁发送的,存在安全隐患。

共享内存针对消息缓冲的缺点改而利用内存缓冲区直接交换信息,无须复制,快捷、信息量大是其优点。但是共享内存的通信方式是通过将共享的内存缓冲区直接附加到进程的虚拟地址空间中来实现的。因此,这些进程之间的读写操作的同步问题操作系统无法实现。必须由各进程利用其他同步工具解决。

另外,由于内存实体存在于计算机系统中.所以只能由处于同一个计算机系统中的诸进程共享。不方便网络通信

消息传递模式

通过操作系统的相应系统调用进行消息传递通讯。分为直接和间接两种:

直接通信方式:点到点的发送
Send (DestProcessName, Message);
Receive (SourceProcessName, Message);
基本思想:进程在发送和接收消息时直接指明接收者或发送者进程ID。
缺点:必须指定接收进程ID。(UNIX的信号机制类似这种形式)
 
间接通信方式:以信箱为媒介进行传递,可以广播
Send (MailBox, Message);
Receive (MailBox, Message);
间接通信方式(信箱命名法)
基本思想:系统为每个信箱设一个消息队列,消息发送和接收都指向该消息队列。
 
缺点:必须有一个通讯双方共享的一个逻辑消息队列( UNIX的PIPE,FIFO及IPC消息传递机制都属于这种形式),使用时消息发送者约定写方式打开信箱,消息接受者约定读方式打开信箱或同时读写打开。
优点:很容易建立双向通讯链(只要对信箱说明为读写打开)。

共享文件模式

管道通信

是一种信息流缓冲机构, UNIX系统中管道基于文件系统,在内核中通过文件描述符表示。管道以先进先出(FIFO)方式组织数据传输。

特点
1.管道是一个单向通信信道,如果进程间要进行双向通信,通常需要定义两个管道。
2.管道通过系统调用read(), write()函数进行读写操作。
 
分类
1.匿名管道:只适用于父子进程之间通信;管道能够把信息从一个进程的地址空间拷贝到另一个进程的地址空间。
2.命名管道:命名管道有自己的名字和访问权限的限制,就像一个文件一样。它可以用于不相关进程间的通信,进程通过使用管道的名字获得管道。

 

CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/CF-855.17.tar.gz 下载到整个 CoreFoundation 的源码。为了方便跟踪和查看,你可以新建一个 Xcode 工程,把这堆源码拖进去看。

Node.js 的事件处理

Windows 程序的消息循环

OSX/iOS 里的 RunLoop

 

NSRunLoop CFRunLoopRef

 

RunLoop 与线程的关系

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里

线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。

RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

你只能在一个线程的内部获取其 RunLoop(主线程除外)。

 

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。

每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

timer    

 NSTimer里面的操作,延时函数

sources source0   主要负责触摸事件,performSelector等等
 

source1

  主要负责触摸事件,performSelector等等
observe 可以观察六种runloop的运行状态:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),//进入runloop
     kCFRunLoopBeforeTimers = (1UL << 1),//准备处理timer
     kCFRunLoopBeforeSources = (1UL << 2),//准备处理source
     kCFRunLoopBeforeWaiting = (1UL << 5),//准备休眠
     kCFRunLoopAfterWaiting = (1UL << 6),//休眠结束,处理事件之前
     kCFRunLoopExit = (1UL << 7),//runloop退出
     kCFRunLoopAllActivities = 0x0FFFFFFFU//所有状态
};
 RunLoop的状态变化,和UI刷新(休眠之前),autorelease(休眠之前)
       
       
       
       
       
       

 

sources source0 source1
 

 source0才是日常开发能接触到的

 

主要负责触摸事件,performSelector等等

基于port的source

 

一般线程间通信都是source1

 

 线程间通讯,系统事件捕获(比如屏幕点击等等)

  source0需要我们手动唤醒线程 source1能唤醒线程
  一般维持一个常驻线程 可以通过添加一个source0来保证runloop不至于因为没有可以处理的事件源或者信号源而退出  
  source0在一次触发后就会被runloop移除 timer和source1(也就是基于port的source)可以反复使用,比如timer设置为repeat,port可以持续接收消息。
举例   CFSocket就是这种形式;source1是基于mach port的事件,程序只要把message塞给port,底层就会触发runloop中对应source1。目前CFMachPort和CFMessagePort是这种形式。以上描述来自CFRunloopSource Reference
版权声明:本文为素染年华原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/liyonghua/p/16438908.html