美文网首页
[OC RunLoop_翻译]五、配置运行循环源

[OC RunLoop_翻译]五、配置运行循环源

作者: Style_月月 | 来源:发表于2020-09-05 00:39 被阅读0次

[OC RunLoop_翻译]一、介绍 & 二、剖析运行循环
[OC RunLoop_翻译]三、 什么时候使用运行循环 & 四、使用运行循环对象
[OC RunLoop_翻译]五、配置运行循环源
注:pdf翻译文档百度云下载链接,密码:zcs2

注: _Run Loops _链接

下面的部分展示了如何在Cocoa和corefoundation中设置不同类型的输入源的示例

5-1、定义自定义输入源

创建自定义输入源需要定义以下内容:

  • 希望输入源处理的信息.
  • 调度程序,让感兴趣的客户知道如何联系您的输入源.
  • 处理程序,用于执行任何客户端发送的请求。.
  • 取消例程,用于使您的输入源无效.

因为您创建了一个自定义输入源来处理自定义信息,所以实际配置的设计是灵活的。调度程序、处理程序和取消例程是自定义输入源几乎总是需要的关键例程。然而,其余的输入源行为大多发生在这些处理程序例程之外。例如,由您定义将数据传递到输入源以及将输入源的存在与其他线程通信的机制。

图3-2显示了自定义输入源的示例配置。在此示例中,应用程序的主线程维护对输入源、该输入源的自定义命令缓冲区以及安装该输入源的运行循环的引用。当主线程有一个要移交给工作线程的任务时,它将一个命令连同工作线程启动该任务所需的所有信息一起发布到命令缓冲区。 (由于主线程和工作线程的输入源都可以访问命令缓冲区,所以该访问必须同步。)一旦发布命令,主线程将向输入源发出信号并唤醒工作线程的运行循环收到唤醒命令后,运行循环将调用输入源的处理程序,该处理程序将处理在命令缓冲区中找到的命令

Figure 3-2 操作自定义输入源

操作自定义输入源

以下部分将解释上图中自定义输入源的实现,并显示需要实现的关键代码。

定义输入源

定义自定义输入源需要使用Core Foundation例程来配置您的运行循环源并将其附加到运行循环。尽管基本处理程序是基于C的函数,但这并不妨碍您编写这些函数的包装程序并使用Objective-C或C ++来实现代码主体。

Figure 3-2 中引入的输入源使用一个Objective-C对象来管理命令缓冲区并与运行循环协调。清单3-3显示了此对象的定义。 RunLoopSource对象管理命令缓冲区,并使用该缓冲区从其他线程接收消息。此清单还显示了RunLoopContext对象的定义,该对象实际上只是一个容器对象,用于传递RunLoopSource对象和对应用程序主线程的运行循环引用

Listing 3-3 自定义输入对象定义

@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.
//这些是CFRunLoopSourceRef回调函数。
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.
//RunLoopContext是在注册输入源时使用的容器对象。
@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对象中的信息进行通信。

Listing 3-4 调度运行循环源

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];
}

最重要的回调例程之一是在输入源被信号通知时用于处理自定义数据的例程。清单3-5显示了与RunLoopSource对象关联的perform回调例程。此函数只是将完成工作的请求转发到sourceFired方法,该方法然后处理命令缓冲区中存在的所有命令。

Listing 3-5 在输入源中执行工作

void RunLoopSourcePerformRoutine (void *info)
{
    RunLoopSource*  obj = (RunLoopSource*)info;
    [obj sourceFired];
}

如果使用 CFRunLoopSourceInvalidate函数从运行循环中删除输入源,系统将调用输入源的取消例程。您可以使用此例程通知客户端您的输入源不再有效,并且应该删除对它的任何引用。清单3-6显示了注册到RunLoopSource对象的取消回调例程。此函数将另一个RunLoopContext对象发送给应用程序委托,但这次请求委托删除对运行循环源的引用。

Listing 3-6 使输入源无效

//从runloop中删除输入源
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];
}

注意:应用程序委托的registerSource:removeSource:方法的代码显示在 Coordinating with Clients of the Input Source

在运行循环上安装输入源

清单3-7显示了RunLoopSource类的initaddToCurrentRunLoop方法。 init方法创建 CFRunLoopSourceRef不透明类型,该类型必须实际附加到运行循环。它会将RunLoopSource对象本身作为上下文信息传递,以便回调例程具有指向该对象的指针。在工作线程调用addToCurrentRunLoop方法之前,不会安装输入源,此时将调用RunLoopSourceScheduleRoutine回调函数。将输入源添加到运行循环后,线程可以运行其运行循环以等待它。

Listing 3-7 初始化runloop输入源

//初始化
- (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;
}
 
//添加到当前的runloop
- (void)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}

与输入源的客户端协调

为了使您的输入源有用,您需要操纵它并从另一个线程发出信号。输入源的全部意义将其关联的线程置于休眠状态,直到有事情要做。这一事实要求应用程序中的其他线程了解输入源并有方法与之通信。

通知客户端输入源的一种方法是在输入源首次安装到其运行循环中时发送注册请求。您可以向任意多个客户注册您的输入源,或者您可以简单地向某个中央机构注册,然后将您的输入源出售给感兴趣的客户。清单3-8显示了由应用程序委托定义的注册方法,并在RunLoopSource对象的调度程序函数被调用时调用。此方法接收RunLoopSource对象提供的RunLoopContext对象并将其添加到其源列表中。此清单还显示了当输入源从其运行循环中移除时用于注销该输入源的例程

Listing 3-8 使用应用程序委托注册和删除输入源

//注册输入源
- (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];
}

注意:清单3-4和清单3-6中显示了调用前面清单中方法的回调函数

向输入源发送信号

在将数据传递给输入源之后,客户端必须向该源发出信号并唤醒其运行循环。发信号给源让运行循环知道源已经准备好被处理。因为当信号出现时线程可能处于休眠状态,所以应该始终显式地唤醒运行循环。否则,可能会导致处理输入源的延迟。

清单3-9显示了RunLoopSource对象的fireCommandsOnRunLoop方法。当客户机准备好让源代码处理它们添加到缓冲区中的命令时,它们会调用此方法。

Listing 3-9 唤醒runloop

// 输入源处理缓存区中的命令
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    //客户端向输入源发送信号
    CFRunLoopSourceSignal(runLoopSource);
    // 唤醒runloop
    CFRunLoopWakeUp(runloop);
}

注意:切勿尝试通过传递自定义输入源来处理SIGHUP或其他类型的进程级信号。用于唤醒运行循环的Core Foundation函数不是信号安全的,不应在应用程序的信号处理程序例程中使用。有关信号处理程序例程的更多信息,请参见 sigaction 手册页。

5-2、配置计时器源

创建计时器源,您要做的就是创建一个计时器对象并将其安排在运行循环中。在Cocoa中,使用 NSTimer类创建新的计时器对象,在Core Foundation中,使用 CFRunLoopTimerRef不透明类型。在内部,NSTimer类只是Core Foundation的扩展,它提供了一些便利功能,例如使用同一方法创建和安排计时器的功能。

Cocoa中,可以使用以下任一类方法一次创建和调度计时器

这些方法会创建计时器,并以默认模式((NSDefaultRunLoopMode)将其添加到当前线程的运行循环中。如果需要,还可以手动调度计时器,方法是创建 NSTimer对象,然后使用 NSRunLoopaddTimer:forMode: 方法将其添加到运行循环中。两种技术基本上都具有相同的作用,但是可以为您提供对计时器配置的不同级别的控制。例如,如果您创建计时器并将其手动添加到运行循环中,则可以使用默认模式以外的其他模式来执行此操作。清单3-10显示了如何使用这两种技术创建计时器。第一个计时器的初始延迟为1秒,但此后每隔0.1秒有规律地触发一次。第二个计时器在最初的0.2秒延迟后开始触发,然后在此之后每0.2秒触发一次。

Listing 3-10 使用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];

清单3-11显示了使用Core Foundation函数配置计时器所需的代码。尽管此示例未在上下文结构中传递任何用户定义的信息,但您可以使用此结构传递计时器所需的任何自定义数据。有关此结构的内容的更多信息,请参见 CFRunLoopTimer Reference中的描述。

Listing 3-11 使用Core Foundation创建和调度计时器

//获取当前的runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();

//CFRunLoopTimerContext 用于配置CFRunLoopTimer的行为
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};

//使用Core Foundation 创建定时器
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
//将定时器加入到当前runloop
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

5-3、配置基于端口的输入源

CocoaCore Foundation都提供了基于端口的对象用于在线程之间或进程之间进行通信。以下各节说明如何使用几种不同类型的端口来设置端口通信。

配置NSMachPort对象

要与 NSMachPort对象建立本地连接,需要创建端口对象并将其添加到主线程的运行循环中。启动辅助线程时,将同一对象传递给线程的入口点函数。辅助线程可以使用同一对象将消息发送回您的主线程。

实现主线程代码

清单3-12显示了启动辅助工作线程的主线程代码。因为Cocoa框架执行许多配置端口和运行循环的中间步骤,所以launchThread方法明显短于其Core Foundation等效方法((Listing 3-17);但是,两者的行为几乎相同。一个区别是,此方法不是将本地端口的名称发送到工作线程,而是直接发送NSPort对象

Listing 3-12 主线程启动方法

{
    //创建 端口对象
    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.
        //将端口添加为当前runloop的输入源。
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
        // Detach the thread. Let the worker release the port.
        //分离线程。并手动释放端口。
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
               toTarget:[MyWorkerClass class] withObject:myPort];
    }
}

为了在线程之间建立双向通信通道,您可能希望工作线程在签入消息中将其自己的本地端口发送到主线程。接收到签入消息可以使您的主线程知道在启动第二个线程时一切进展顺利,还为您提供了一种向该线程发送更多消息的方法。

清单3-13显示了主线程的 handlePortMessage: 方法。当数据到达线程自己的本地端口时调用此方法。当签入消息到达时,该方法直接从端口消息中检索辅助线程的端口,并将其保存以供以后使用。

Listing 3-13 处理Mach端口消息

#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.处理其他消息
    }
}
实现辅助线程代码

对于辅助工作线程,必须配置线程并使用指定的端口将信息传递回主线程.

清单3-14显示了设置工作线程的代码。在为线程创建自动释放池之后,该方法创建一个辅助对象来驱动线程的执行。worker对象的sendCheckinMessage:方法(如 Listing 3-15所示)为工作线程创建一个本地端口,并将签入消息发送回主线程。

Listing 3-14 使用Mach端口启动工作线程

// 使用Mach端口启动工作线程+(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];
}

使用NSMachPort时,本地线程和远程线程可以将同一端口对象用于线程之间的单向通信。换句话说,一个线程创建的本地端口对象成为另一线程的远程端口对象

清单3-15显示了辅助线程的签入例程。此方法设置自己的本地端口以用于将来的通信,然后将签入消息发送回主线程。该方法使用在LaunchThreadWithPort:方法中接收的端口对象作为消息的目标。

Listing 3-15 使用Mach端口发送签入消息

// 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对象

要与 NSMessagePort对象建立本地连接,不能简单地在线程之间传递端口对象。远程消息端口必须按名称获取。在Cocoa中实现这一点需要用一个特定的名称注册本地端口,然后将该名称传递给远程线程,以便它可以获得适当的端口对象进行通信。清单3-16显示了在需要使用消息端口的情况下创建和注册端口的过程。

Listing 3-16 注册消息端口

//创建本地端口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中配置基于端口的输入源

本节介绍如何使用Core Foundation在应用程序的主线程和工作线程之间设置双向通信通道。

清单3-17显示了应用程序的主线程调用以启动工作线程的代码。代码所做的第一件事是设置一个 CFMessagePortRef不透明类型来侦听来自工作线程的消息。工作线程需要连接端口的名称,以便将字符串值传递到工作线程的入口点函数。端口名在当前用户上下文中通常应该是唯一的;否则,您可能会遇到冲突。

Listing 3-17 将Core Foundation消息端口附加到新线程

#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.
        // 将run loop source添加到当前runloop
            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));
}

安装了端口并启动了线程后,主线程可以在等待线程签入时继续其常规执行。当check-in消息到达时,它被调度到主线程的MainThreadResponseHandler函数,如清单3-18所示。此函数用于提取工作线程的端口名,并为将来的通信创建管道。

Listing 3-18 接收签入消息

#define kCheckinMessage 100
 
// Main thread port message handler 主线程端口消息处理程序
CFDataRef MainThreadResponseHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void* info)
{
    if (msgid == kCheckinMessage) //如果是check-in消息
    {
        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.
        //保留并保存线程的comm端口以备将来参考
            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;
}

配置了主线程后,剩下的唯一任务就是让新创建的工作线程创建自己的端口并签入。清单3-19显示了工作线程的入口点函数。该函数提取主线程的端口名,并使用它来创建回主线程的远程连接。然后,该函数为自己创建一个本地端口,在线程的运行循环中安装该端口,并向主线程发送一个包含本地端口名的签入消息。

Listing 3-19 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.释放在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();
}

一旦进入运行循环,所有发送到线程端口的事件都由ProcessClientRequest函数处理。该函数的实现取决于线程所做的工作的类型,这里不显示。

相关文章

网友评论

      本文标题:[OC RunLoop_翻译]五、配置运行循环源

      本文链接:https://www.haomeiwen.com/subject/wvttektx.html