Run Loops
来源:互联网 发布:vincizhang淘宝上有吗 编辑:程序博客网 时间:2024/05/17 05:02
原文地址:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
运行循环是与线程相关联的重要的基础架构的一部分。运行循环是一个用于调度工作和协调接收事件的事件处理循环。运行循环的目的是让你的线程在有事情做的时候忙起来,在没有事情的时候休眠。
运行循环管理不完全是自动的。您仍然须设计您的线程的代码,以在适当的时间启动运行循环,并响应传入的事件。 Cocoa 和 Core Foundation都提供了运行循环对象来帮助配置和管理你的线程的运行循环。您的应用程序不需要显式地创建这些对象;每个线程,包括应用程序的主线程,都有一个相关的运行循环对象。然而,只有辅助线程需要显式地运行它们的运行循环。应用程序框架将在主线程上自动设置并运行运行循环,作为应用程序启动过程的一部分。以下部分提供了关于运行循环的详细信息,以及在应用程序中如何配置它们。有关运行循环对象的更多信息,参考 NSRunLoop Class Reference 和 CFRunLoop Reference。
运行循环的分析(Anatomy of a Run Loop)
运行循环就像它的名字一样。它是一个你的线程进入并用于运行事件处理程序以响应传入的事件的循环。你的代码提供控制语句来实现run loop实际的循环部分,换句话说,就是你的代码提供while或者for循环来驱动run loop,在你的循环中,你使用run loop对象来“运行”接受事件和调用已安装的处理的事件处理代码。
运行循环接收来自两种不同类型的源的事件。输入源(Input sources)传递异步事件,通常是来自另一个线程的消息或来自不同应用程序的消息。定时器源(Timer sources)提供同步事件,发生在一个预定的时间或重复间隔。这两种类型的源使用一个特定于应用程序的处理程序来处理它到达的事件。
图3-1显示了一个运行循环结构和各种源。输入源(Input sources)传递异步事件到相应的处理程序并引发runUntilDate:方法(被线程的相关的NSRunLoop对象调用)的退出。定时器源( Timer sources)将事件传递给他们的处理程序,但不会导致运行循环退出。
除了处理输入源,运行循环还生成关于运行循环运行状态的通知。注册运行循环观察者可以接收这些通知,并使用它们在线程上做额外的处理。您使用Core Foundation在您的线程上安装运行循环的观察者。
以下部分提供了运行循环组件的详细信息,以及它们所运行的模式。还描述了在事件处理过程中的不同时间产生的通知。
运行循环模式(Run Loop Modes)
运行循环模式是被监听的输入源和定时器源的集合,也是运行循环被通知的观察者的集合。每一次你运行你的run loop的时候,你都要给它指定(无论是显式或隐式)一个特定的“模式”,在运行循环期间,仅仅和这个模式相关联的源才会被监听并被允许传递它们的事件。(相同的,只有与该模式相关联的观察者才会被通知)和其他模式关联的源会挂起任何新的事件直到后续的事件在适当的模式经过循环。
在你的代码中,你通过名称识别模式。Cocoa 和 Core Foundation都定义一个默认的模式和几种通用模式,以及在您的代码中指定这些模式的字符串。通过简单地为模式名称指定一个自定义字符串,可以定义自定义模式。虽然你给自定义模式分配的名称是任意的,但这些模式的内容不是。你必须确定添加一个或者多个输入源,定时器源或者run-loop观察者到你为它们创建的任意模式是可用的。
在一个特定运行循环的过程中,您使用模式来过滤掉不需要的源事件。大多数时候,您将要在系统定义的“默认”模式下运行您的运行循环。一个模态分组可能会运行在“模态”模式下。在这种模式下,只有和模态分组相关的源才能将事件传递到线程。对于辅助线程,您可以使用自定义模式来阻止低优先级的源在时间紧急操作期间传递事件。
注意:模式区分基于事件的源,而不是事件的类型。例如,你不能使用模式来匹配仅仅鼠标按下事件或仅仅键盘事件。您可以使用模式来监听不同的端口,暂时停止定时器,或者其他方式改变正在被监听的源和run loop观察者
表3-1列出的Cocoa 和 Core Foundation定义的标准模式,以及你何时使用这些模式的描述。名称列列出了您在代码中用来指定模式的实际常量。
Predefined run loop modes
Mode
Name
Description
Default
NSDefaultRunLoopMode
(Cocoa)
kCFRunLoopDefaultMode
(Core Foundation)
默认模式是用于大多数操作的一个默认模式。大多数时候,你应该使用这个模式来启动你的运行循环,并配置你的输入源。
Connection
NSConnectionReplyMode
(Cocoa)
Cocoa使用NSConnection对象结合这种模式来监听响应。你应该很少需要使用这种模式。
Modal
NSModalPanelRunLoopMode
(Cocoa)
Cocoa使用这种模式来定义用于模态分组的事件
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)
这是一个可配置通用模式的分组。将一个输入源与此模式关联,也会将其与组中的每个模式关联。对于Cocoa应用程序,此集合包括默认的、模态的和默认的事件跟踪模式。Core Foundation最初只包括默认模式。您可以使用CFRunLoopAddCommonMode功能添加自定义模式到集合中。
输入源(Input sources)
输入源以异步方式将事件发送到您的线程中。事件的源取决于输入源的类型,而输入源的类型一般分为两类。基于端口的输入源监听您的应用程序的Mach端口。自定义输入源监听自定义事件源。就您的运行循环而言,无论输入源是基于端口还是自定义都无关紧要。系统通常实现了两种类型的输入源,您可以直接使用。这两个源之间唯一的区别是他们是如何发出信号的。基于端口的源是由内核自动发出信号的,自定义源必须从另一个线程手动发出信号。
当您创建一个输入源时,您将它分配给运行循环的一个或多个模式。在任何给定的时刻模式都影响被监听的输入源。大多数时候,在默认模式下运行运行循环,但也可以指定自定义模式。如果一个输入源不在当前监听的模式中,它产生的任何事件都会被挂起,直到运行循环运行在正确的模式下为止。
下面的部分描述了一些输入源。
基于端口的源(Port-Based Sources)
Cocoa 和 Core Foundation提供内置的支持来创建基于端口的输入源通过使用端口相关的对象和功能。例如,在Cocoa,你永远不需要直接创建一个输入源。您只需创建一个端口对象并使用NSPort的方法来添加端口到run loop。端口对象为您处理所需的输入源的创建和配置。
在Core Foundation,您必须手动创建端口和它的run loop源。在这两种情况下,你使用的端口类型的功能(CFMachPortRef
, CFMessagePortRef
, 或 CFSocketRef
)来创建相应的对象。
关于如何设置和配置自定义基于端口的源的示例,请参见Configuring a Port-Based Input Source.
自定义输入源(Custom Input Sources)
要创建一个自定义的输入源,你必须使用Core Foundation中CFRunLoopSourceRef类型相关联的功能。你使用多个回调函数来配置一个自定义的输入源。Core Foundation调用这些函数在不同的点来配置源,处理任何传入的事件,并销毁源在它从run loop被删除的时候。
除了定义事件到达时的自定义源的行为外,还必须定义事件传递机制。该源的这部分运行在一个单独的线程,并负责提供输入源和它的数据以及在这些数据准备好处理的时候发出信号。事件传递机制取决于你,但不需要过于复杂。
对于如何创建一个自定义输入源的示例,请参见Defining a Custom Input Source。对于自定义输入源的参考信息,参考CFRunLoopSource Reference。
Cocoa执行选择器源(Cocoa Perform Selector Sources)
除了基于端口的源,Cocoa定义了一个自定义的输入源,允许您在任何线程上执行一个选择器。像一个基于端口的源,在目标线程执行选择器请求是序列化的,缓解很多同步问题当很多方法在同一个线程上运行的时候。与基于端口的源不同,一个执行选择器源会在执行它的选择器后从运行循环中移除它自己。
注:OS X v10.5及之前版本,执行选择源主要用于发送消息到主线程,但是在OS X v10.5及以后版本以及在iOS中,你可以使用它们来发送消息到任何线程。
当在另一个线程上执行一个选择器时,目标线程必须有一个活跃的运行循环。对于您创建的线程,这意味着等待,直到您的代码显式启动运行循环。因为主线程启动自己的运行循环,当应用程序委托调用applicationDidFinishLaunching:方法的时候你可以在主线程开始发起调用。运行循环在每次循环中都会处理所有排队的执行选择器 ,而不是在每次循环迭代过程中只处理一个。
表3-2列举了NSObject定义的可以用来在其他线程执行选择器的方法。因为这些方法都声明在NSObject,你可以在任何你已经获得Objective-C对象的线程使用它们,包括POSIX线程。这些方法实际上并没有创建一个新的线程来执行选择器。
Methods
Description
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
在应用程序的主线程的下一个循环周期期间在其上面执行特定的选择器,这些方法给你提供阻塞当前线程直到选择器被执行的选项。
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
在任何一个你持有NSThread对象的线程上面执行特定的选择器,这些方法给你提供阻塞当前线程直到选择器被执行的选项。
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
在下一个运行循环周期和一个可选的延迟期后,在当前线程上执行指定的选择器。因为它等待到下一个运行循环周期来执行选择器,这些方法提供了一个从当前正在执行的代码的自动迷你延迟。多个排队的选择器一个接一个按顺序被执行。
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
使你取消使用 the performSelector:withObject:afterDelay:
或 performSelector:withObject:afterDelay:inModes:
方法产生的将一个消息的发送到当前线程的事件 .
这些方法的详细信息, 参考 NSObject Class Reference.
定时器源(Timer Sources)
定时器源传递同步事件到你的线程在未来的一个预设的时间。定时器是一个线程通知自己做某事的一种方式。例如,一个查询栏可以使用一个定时器来在用户输入一连串的关键字以后的特定时间启动一个自动搜索。这个延迟时间的使用在开始搜索之前给了用户一个键入尽可能匹配的搜索字符串的机会。
虽然它产生基于时间的通知,定时器并不是实时机制。像输入源一样,定时器与运行循环的特定模式相关联。如果一个计时器不在当前运行循环正在监听的模式中,它不会被启动,直到你在一个定时器的支持的模式中运行运行循环。同样,如果在运行循环执行处理程序期间触发定时器,定时器将等到下一次通过运行循环来调用它的处理程序。如果运行循环没有运行,则定时器永远不会被触发。
您可以配置定时器来生成一次或者重复的事件。一个重复的定时器会基于预定启动的时间来自动重新安排自身,而不是基于实际的触发时间。例如,如果一个定时器被安排在一个特定的时间触发,并且重复周期为5秒,预定的触发时间将总是落在原始的5秒的时间间隔,即使实际触发时间被延迟。如果触发时间延迟了很多,以致于它错过了一个或多个预定的触发时间,定时器仅会为错过的时间周期触发一次。在这一次周期以后,定时器会为下一个预定的触发时间重新定时。
有关配置定时器源的更多信息,请参见 Configuring Timer Sources。相关信息,参见 NSTimer Class Reference 或 CFRunLoopTimer Reference。
运行循环观察者(Run Loop Observers)
和源相比,当一个适当的异步或同步事件发生时会触发源,而运行循环观察者会在特定的运行循环自身执行过程中的某个位置被触发。您可能会使用运行循环的观察者来准备您的线程来处理一个给定的事件或准备线程在它将要休眠之前。您可以将运行循环观察者与您的运行循环中的下列事件关联:
进入运行循环。
当运行循环将要处理一个定时器时。
当运行循环将要处理一个输入源时。
当运行循环将要休眠。
当运行循环已被唤醒,但是在这之前它已经处理唤醒事件。
退出运行循环。
您可以使用Core Foundation添加运行循环观察者到应用程序。要创建一个运行循环的观察者,你要创建一个新的CFRunLoopObserverRef类型的实例。这种类型保持跟踪你的的自定义回调函数的跟踪和它感兴趣的活动。
类似定时器,运行循环的观察者可以使用一次或多次。一个一次性的观察者会在自身被触发以后从运行循环移除,而一个重复的观察者会保持附属。你创建一个观察者的时候来指定它是否运行一次或多次。
对于如何创建一个运行环观察者的示例,请参见 Configuring the Run Loop。更多信息,参见 CFRunLoopObserver Reference。
事件的运行循环顺序(The Run Loop Sequence of Events)
每次运行它时,您的线程的运行循环处理挂起的事件,并生成任何附属的观察者的通知。它处理这些的顺序是非常明确的,如下:
1.通知运行循环已进入的观察者。
2.通知观察者,任何准备就绪的定时器即将触发。
3.通知观察者,任何不基于端口的输入源都将触发。
4.触发任何准备就绪的基于非端口的输入源。
5.如果一个基于端口的输入源已准备就绪并等待触发,则立即处理事件。转到步骤9。
6.通知观察者,线程即将休眠。
7.将线程休眠,直到下列事件之一发生:
*一个事件到达一个基于端口的输入源。
*定时器触发。
*运行循环超时。
*运行循环被显式唤醒。
8.通知观察者,线程被唤醒。
9.处理挂起的事件。
*如果一个用户定义的定时器被触发,处理定时器事件并重新启动循环。转到步骤2。
*如果一个输入源被触发,传递事件。
*如果运行循环被显式唤醒,但还没有超时,重新启动循环。转到步骤2。
10.通知观察者,运行循环已退出。
由于定时器和输入源的观察者通知在他们的事件实际发生之前被传递,因此可能会有通知的时间和实际事件的时间之间的间隔。如果这些事件之间的时间是关键的,你可以使用休眠和唤醒休眠通知,以帮助您关联的实际事件之间的时间。
因为定时器和其他周期性事件在你运行运行循环的时候被传递,规避循环会破坏那些事件的传递。这种行为的典型例子是,每当你通过进入一个循环,并从应用程序重复请求事件来实现一个鼠标跟踪程序时。因为您的代码是直接抓取事件,而不是让应用程序正常调度这些事件, 所以直到您的鼠标跟踪程序退出并返回到应用程序以后,活跃的定时器将不能被激活。
使用运行循环对象可以显式地唤醒运行循环。其他事件也可能导致运行循环被唤醒。例如,添加另一个非端口的输入源唤醒运行循环,以便输入源可以立即被处理,而不是等到其他事件发生。
你什么时候会使用一个运行循环?(When Would You Use a Run Loop?)
只有当您为应用程序创建辅助线程时,才会显式运行一个运行循环。应用程序的主线程的运行循环是基础结构的一个关键部分。因此,应用程序框架提供了运行主应用程序循环并自动启动该循环的代码。在iOS中UIApplication的运行方法(或OS X中 NSApplication)启动应用程序的主循环作为正常启动程序的一部分。如果你使用Xcode项目模板来创建你的应用,你不用显示的调用这些代码。
对于辅助线程,你需要决定是否运行循环是必要的,如果它是,请自行配置和启动它。在所有情况下,您都不需要启动一个线程的运行循环。例如,如果您使用一个线程执行一些长时间运行和预定的任务,您可能会避免启动运行循环。运行循环是用于你想要更多的与线程交互的情况下。例如,如果您打算做以下任何一个,您需要启动一个运行循环:
使用端口或自定义输入源与其他线程进行通信。
在线程上使用定时器。
在Cocoa应用中使用任何的performSelector…方法。
保持线程执行周期性任务。
如果您选择使用一个运行循环,配置和安装是简单的。不过,和所有的线程编程一样,你应该有一个在适当的情况下退出你的辅助线程的计划。最好是通过让它自己退出的方式来规范地终止一个线程,而不是迫使它终止。如何配置和退出运行循环的信息参见 Using Run Loop Objects。
使用运行循环对象(Using Run Loop Objects)
一个运行循环对象提供了添加输入源、定时器和运行循环观察者到您的运行循环,然后运行它的主要接口。每一个线程都有一个与它关联的单独的运行循环对象。在Cocoa中,这个对象是 NSRunLoop
类的一个实例。在一个低层的应用程序,它是一个指向CFRunLoopRef
类型的指针。
获取一个运行循环对象(Getting a Run Loop Object)
要获取当前线程的运行循环,使用下列方法之一:
*在Cocoa应用程序,使用NSRunLoop的 currentRunLoop
类方法来检索一个NSRunLoop对象。
*使用CFRunLoopGetCurrent函数。
虽然他们不是桥接的的类型,你可以在需要的时候从NSRunLoop对象获取一个CFRunLoopRef类型。NSRunLoop类定义了一个 getCFRunLoop
方法,返回一个CFRunLoopRef类型,你可以传递到 Core Foundation程序。因为两个对象引用同一个运行循环,所以你可以在需要的时候交叉调用NSRunLoop对象和CFRunLoopRef类型。
配置运行循环(Configuring the Run Loop)
在辅助线程上运行一个运行循环之前,你必须向它添加至少一个输入源或计时器。如果运行循环没有任何源来监听,当您尝试运行它时,它会立即退出。对于如何向运行循环中添加源的示例,请参见Configuring Run Loop Sources。
除了安装源外,还可以安装运行循环观察者,并使用它们来检测运行循环的不同执行阶段。要安装运行循环的观察者,你需要创建一个CFRunLoopObserverRef
类型并使用的 CFRunLoopAddObserver函数
添加到您的运行循环。运行循环的观察者必须使用Core Foundation创建,即使在Cocoa应用程序。
列表3-1显示将一个运行循环观察者附属到一个线程的运行循环的主程序。该示例的目的是向您展示如何创建一个运行循环观察者,因此代码简单地设置了一个运行环观察者来监听所有的运行循环事件。基本处理程序(未显示)简单地记录运行循环活动,当它处理定时器请求时。
Creating a run loop observer
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
有几种方法来启动运行循环,包括以下:
有一个设定时间限制
在一个特定的模式
NSRunLoop
类的方法以类似的方式来运行运行循环并且不需要检查返回值。(一个运行循环调用NSRunLoop类的方法的一个例子,参见 Listing 3-14。)Running a run loop
- (void)skeletonThreadMain
{
// Set up an autorelease pool here if not using garbage collection.
BOOL done = NO;
// Add your sources or timers to the run loop and do any other setup.
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
// If a source explicitly stopped the run loop, or if there are no
// sources or timers, go ahead and exit.
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
done = YES;
// Check for any other exit conditions here and set the
// done variable as needed.
}
while (!done);
// Clean up code here. Be sure to release any allocated autorelease pools.
}
CFRunLoopRun
, CFRunLoopRunInMode
, 或任何NSRunLoop的方法从输入源或定时器处理程序内部来启动运行循环。当这样做时,您可以使用任何您想要的模式来运行嵌套的运行循环,包括外层运行循环使用的模式。给运行循环配置超时值。
告诉运行循环停止。
CFRunLoopStop函数来显式地终止运行循环产生和超时相同的结果。运行循环发出任何余留的运行循环通知然后退出。不同的是,你可以在你无条件地启动运行循环的时候使用这种技术
。
Cocoa的NSRunLoop类并不像Core Foundation那样天生是线程安全的。如果你正在使用NSRunLoop类来修改您的运行循环,你应该只在拥有运行循环的同一个线程这样做。添加一个输入源或定时器到一个不同的线程的运行循环可能会导致你的代码崩溃或一个无法预料的结果。
下面的章节展示了如何在Cocoa 和 Core Foundation上设置不同类型的输入源的例子。
定义一个自定义输入源(Defining a Custom Input Source)
*一个让感兴趣的客户短知道如何与您的输入源联系的调度程序。
* 任何客户端可发送的执行请求的处理程序。
*一个是你的输入源无效的取消程序。
因为您创建了一个自定义的输入源来处理自定义信息,因此实际的配置被设计为灵活的。调度程序、处理程序和取消程序是通常是您自定义输入源的关键程序。大多数的余下的输入源的行为发生在这些处理程序之外。例如,由你来定义将数据传递给您的输入源的机制,以及传达输入源的存在到其他线程的机制。
图3-2显示了一个自定义输入源的配置示例。在这个示例中,应用程序的主线程保持对输入源,输入源的自定义命令缓冲区,以及输入源被安装的运行循环的引用。当主线程有一个任务需要移交给工作线程的时候,它抛出一个携带工作线程需要的任何信息的命令到命令缓冲区来启动任务。(因为主线程和工作线程的输入源都有访问命令缓冲区,访问必须是同步的。) 一旦命令被抛出,主线程通知输入源并唤醒工作线程的运行循环。在接收到唤醒命令后,运行循环调用输入源的处理程序来处理在命令缓冲区找到的命令。
定义输入源(Defining the Input Source)
Figure 3-2 中的输入源使用Objective-C对象管理命令缓冲区并使用运行循环进行协调协调。列表3-3显示该对象的定义。RunLoopSource对象管理一个命令缓冲区并使用这个缓冲区接收来自其他线程的消息。这个清单还显示了RunLoopContext对象的定义,这个对象实际上是一个用来传递RunLoopSource对象和一个运行循环引用到应用程序主线程的容器对象。
The custom input source object definition
@interface RunLoopSource : NSObject
{
CFRunLoopSourceRef runLoopSource;
NSMutableArray* commands;
}
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
// Handler method
- (void)sourceFired;
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
@end
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
CFRunLoopRef runLoop;
RunLoopSource* source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end
虽然Objective-C代码管理输入源的自定义数据,但是将输入源附属到运行循环需要基于C的回调函数。第一个函数在你实际的附属运行循环源到你的运行循环的时候被调用,如清单3-4所示。因为这个输入源只有一个客户端(主线程),它通过调度函数来发送一个消息来向该线程上的应用程序委托注册自己。当委托希望与输入源通信的时候,它可以利用在RunLoopContext中的信息。
Scheduling a run loop source
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext waitUntilDone:NO];
}
最重要的回调函数之一是当输入源被触发时用来处理自定义数据。Listing3-5显示和RunLoopSource对象关联的执行回调函数。这个函数简单的转发请求到sourceFired方法来做处理,然后处理在命令缓冲区的任何命令。
Performing work in the input source
void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
Invalidating an input source
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext waitUntilDone:YES];
}
Note: 应用程序委托的registerSource:
和 removeSource:
方法的代码在Coordinating with Clients of the Input Source.
在运行环上安装输入源(Installing the Input Source on the Run Loop)
在运行循环上安装输入源(Installing the Input Source on the Run Loop)
Listing3-7显示的RunLoopSource类的init和addToCurrentRunLoop方法。init方法创建 CFRunLoopSourceRef
类型,它必须连接到运行环。它将RunLoopSource对象本身作为上下文信息传递,这样的回调例程就有一个指向该对象的指针。输入源的安装直到工作线程调用addToCurrentRunLoop方法的时候才会触发,此时,RunLoopSourceScheduleRoutine回调函数也被调用。一旦输入源添加到运行循环,线程可以运行它的运行循环来等待。
Installing the run loop source
- (id)init
{
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
commands = [[NSMutableArray alloc] init];
return self;
}
- (void)addToCurrentRunLoop
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
Registering and removing an input source with the application delegate
- (void)registerSource:(RunLoopContext*)sourceInfo;
{
[sourcesToPing addObject:sourceInfo];
}
- (void)removeSource:(RunLoopContext*)sourceInfo
{
id objToRemove = nil;
for (RunLoopContext* context in sourcesToPing)
{
if ([context isEqual:sourceInfo])
{
objToRemove = context;
break;
}
}
if (objToRemove)
[sourcesToPing removeObject:objToRemove];
}
Note: 在前面的清单中调用这些方法的回调函数展示在 Listing 3-4 和 Listing 3-6.
向输入源发信号(Signaling the Input Source)
在它将数据移交给输入源后,客户端必须对源发信号,并唤醒其运行循环。以信号告知源可以让运行循环知道源已准备好被处理。而且因为当信号发生时,线程可能在休眠,你应该总是显式地唤醒运行循环。不这样做可能会导致处理输入源的延迟。
Listing3-9显示RunLoopSource对象的fireCommandsOnRunLoop方法。客户端会在它们准备好源来处理它们添加到缓冲区的命令的时候调用它。
Waking up the run loop
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
CFRunLoopSourceSignal(runLoopSource);
CFRunLoopWakeUp(runloop);
}
配置定时器源(Configuring Timer Sources)
要创建一个计时器源,你所要做的就是创建一个定时器对象,并将它安排在运行的循环中。在Cocoa中,你使用NSTimer
类来创建新的定时器对象,并在Core Foundation中你使用CFRunLoopTimerRef类型。实质上,NSTimer类仅仅是Core Foundation的一个扩展,它提供了一些方便的功能,如使用相同的方法创建和安排一个定时器的能力。
在Cocoa中,您可以使用这些类方法中的任何一个创建和调度一个定时器:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
NSDefaultRunLoopMode
)。你也可以在你需要的时候通过创建你的 NSTimer
对象并通过 NSRunLoop
的
addTimer:forMode:
方法将它添加到运行循环的方式来手动的调度一个运行循环。这两种技术做基本上相同的事情,但给你不同程度的控制定时器的配置的权利。例如,如果您创建了定时器,并手动添加到运行循环中,您可以使用默认模式以外的其他进行操作。Listing3-10显示如何使用这两种技术创建定时器。第一个定时器有一个1秒的初始延迟,但随后每隔0.1秒就触发一次。第二个定时器在最初的0.2秒的延迟后开始启动,然后每0.2秒触发一次。Creating and scheduling timers using NSTimer
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:@selector(myDoFireTimer1:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(myDoFireTimer2:)
userInfo:nil
repeats:YES];
Listing3-11显示使用Core Foundation功能配置一个定时器所需的代码。虽然这个示例没有传递任何用户自定义的使用上下文结构的信息,但您可以使用此结构来传递您所需的定时器的任何自定义数据。关于这个结构的内容的更多信息,见CFRunLoopTimer Reference。
Creating and scheduling a timer using Core Foundation
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
&myCFTimerCallback, &context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
NSMachPort对象
建立一个本地连接,你需要创建端口对象并将其添加到您的主线程的运行循环。在启动辅助线程时,您将同一个对象传递到您的线程的入口点函数。辅助线程可以使用同一个对象将消息发送回主线程。实现主线程代码
Listing3-12显示启动一个辅助线程的主线程的代码。因为Cocoa框架执行许多中间步骤来配置端口和运行循环,launchThread方法明显短于其在Core Foundation中的实现(Listing 3-17);然而,两者的行为几乎是相同的。有一个区别是,不是发送本地端口的名称到工作者线程,这个方法直接发送NSPort对象。
- (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
// This class handles incoming port messages.
[myPort setDelegate:self];
// Install the port as an input source on the current run loop.
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Detach the thread. Let the worker release the port.
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class] withObject:myPort];
}
}
handlePortMessage:
方法。当数据到达线程持有的的本地端口时,这个方法被调用。当签入消息到达时,该方法将直接从该端口消息中检索辅助线程的端口,并将其保存为以后使用。#define kCheckinMessage 100
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
unsigned int message = [portMessage msgid];
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
// Get the worker thread’s communications port.
distantPort = [portMessage sendPort];
// Retain and save the worker port for later use.
[self storeDistantPort:distantPort];
}
else
{
// Handle other messages.
}
}
Listing3-14展示设置工作线程的代码。在为线程创建一个自动释放池以后,方法创建一个工作线程来驱动线程执行。工作对象的sendCheckinMessage:方法 (s展示于 Listing 3-15)为线程创建一个本地端口并发送一个签入消息到主线程。
Launching the worker thread using Mach ports
+(void)LaunchThreadWithPort:(id)inData
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Set up the connection between this thread and the main thread.
NSPort* distantPort = (NSPort*)inData;
MyWorkerClass* workerObj = [[self alloc] init];
[workerObj sendCheckinMessage:distantPort];
[distantPort release];
// Let the run loop process things.
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];
}
Listing3-15显示辅助线程的签入程序。这种方法为未来的通信设置了自己的本地端口,然后将签入消息发送回主线程。该方法使用从LaunchThreadWithPort:方法接收的端口对象作为消息的目标。
Sending the check-in message using Mach ports
// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
// Retain and save the remote port for future use.
[self setRemotePort:outPort];
// Create and configure the worker thread port.
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Create the check-in message.
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
receivePort:myPort components:nil];
if (messageObj)
{
// Finish configuring the message and send it immediately.
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
}
使用
NSMessagePort对象
建立一个本地连接,你不能简单的在线程之间传递端口对象。远程消息端口必须通过名称获取。在Cocoa中要使这个变得可能需要通过一个指定的名称来注册你的本地端口,然后将该名称传递给远程线程,以便它可以获得适当的端口对象来通信。Listing3-16显示你要使用消息端口的时候端口创建和注册的过程。Registering a message port
NSPort* localPort = [[NSMessagePort alloc] init];
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
基于Core Foundation配置一个基于端口的输入源(Configuring a Port-Based Input Source in Core Foundation)
本节展示如何基于Core Foundation在应用程序的主线程和工作线程之间建立一个双向通信信道。
Listing3-17显示应用程序的主线程调用以启动工作线程的代码。代码处理的第一件事是,设置一个CFMessagePortRef
类型变量来监听来自工作线程的消息。工作线程需要端口的名称来建立连接,因此字符串值被传递给工作线程的入口函数。端口名称通常在当前用户上下文中是唯一的;否则,会导致冲突。
Attaching a Core Foundation message port to a new thread
#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
// Create a local port for receiving responses.
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
// Create a string with the port name.
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
// Create the port.
myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
// The port was successfully created.
// Now create a run loop source for it.
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (rlSource)
{
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
}
}
// Create the thread and continue processing.
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint,
(void*)myPortName,
kThreadStackSize,
NULL,
NULL,
NULL,
0,
&taskID));
}
Receiving the checkin message
#define kCheckinMessage 100
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
SInt32 msgid,
CFDataRef data,
void* info)
{
if (msgid == kCheckinMessage)
{
CFMessagePortRef messagePort;
CFStringRef threadPortName;
CFIndex bufferLength = CFDataGetLength(data);
UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
// You must obtain a remote message port by name.
messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
if (messagePort)
{
// Retain and save the thread’s comm port for future reference.
AddPortToListOfActiveThreads(messagePort);
// Since the port is retained by the previous function, release
// it here.
CFRelease(messagePort);
}
// Clean up.
CFRelease(threadPortName);
CFAllocatorDeallocate(NULL, buffer);
}
else
{
// Process other messages.
}
return NULL;
}
Setting up the thread structures
OSStatus ServerThreadEntryPoint(void* param)
{
// Create the remote port to the main thread.
CFMessagePortRef mainThreadPort;
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
// Free the string that was passed in param.
CFRelease(portName);
// Create a port for the worker thread.
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
// Store the port in this thread’s context info for later reference.
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&ProcessClientRequest,
&context,
&shouldFreeInfo);
if (shouldFreeInfo)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
// Package up the port name and send the check-in message.
CFDataRef returnData = nil;
CFDataRef outData;
CFIndex stringLength = CFStringGetLength(myPortName);
UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
CFStringGetBytes(myPortName,
CFRangeMake(0,stringLength),
kCFStringEncodingASCII,
0,
FALSE,
buffer,
stringLength,
NULL);
outData = CFDataCreate(NULL, buffer, stringLength);
CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL);
// Clean up thread data structures.
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
// Enter the run loop.
CFRunLoopRun();
}
- Run Loops
- Run Loops
- Run Loops
- Run loops
- Run Loops
- Run Loops
- Run Loops
- Run loops的使用
- Run Loops(翻译)
- 关于Run Loops
- iOS开发笔记--Run Loops
- GCD && Run Loops学习笔记
- 运行循环(Run Loops)
- iOS:多线程编程指南(二)--Run Loops
- Run Loops 是个什么东西。
- iOS开发笔记--Run Loops(简洁版)
- Run Loops官方文档翻译(一)
- loops
- apk瘦身
- 用户是否登录
- jira Error connecting to database
- python中判断文件编码的chardet
- Android OTA升级的补救措施
- Run Loops
- caffe源码vision_layers.hpp+各cpp
- FFmpeg视频编解码库,无法解析的外部信号
- ubunbut下文字转图片报错, convert : unable to read font
- iOS类型编码Type Encodings与属性类型Property Type
- iOS钥匙串返回-50
- SQL的内连接与外连接
- android 流布局 FlowLayout
- Map遍历方式