scheduleInRunLoop - 线程化网络连接

9

我还没有找到任何好的文档来解释NSStream的线程处理过程。具体来说,我们来看NSInputStream。对我来说,Objective-C中的线程处理目前是一个谜,因为它似乎非常简单。

我的问题主要涉及这行代码:

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

您可以指定输入流将在哪个运行循环中运行,我认为这非常酷。问题是,如果我想让输入和输出流在它们自己的线程中运行,并且两者都在单个类(比如 Connection)中实例化,那么该怎么办才能让它们在自己的线程中运行?
我之所以问这个问题是因为代理。以前我们会做 [inputStream setDelegate:self],这意味着我们必须声明 stream:handleEvent 来处理传入/传出数据。
因此,我的问题是,如果您有一个设置输入和输出流的类,那么如何将每个流线程化并委托处理流事件的责任给当前类?
以下是一些代码:
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];

我在考虑以下事项:
  • 你不能将当前类中的两个线程的责任委托给同一个对象,你必须要将其分别委托给不同的对象。
  • 一个线程是否能同时处理这两个流?(我个人认为不行,因为输入输出会同时运行)
  • 我在错误地考虑这个问题,你可以创建一个单独的运行循环并针对某些单独的线程调用scheduleRunLoop吗?
有什么想法吗?

如果您创建了一个NSThread,您也将可以访问它所拥有的NSRunLoop。反过来则不行,访问NSRunLoop并不会为您创建NSThread。如果您请求currentRunLoop,则会获取当前线程的runloop。 - Ol Sen
2个回答

2
请注意之间的区别。

每个线程都有自己的栈,但所有线程都访问同一个堆。

输入流需要自己的线程,因为在没有到达任何EOF的情况下读取流时,如果没有新字符出现,线程会阻塞并等待。因此,为了防止应用程序被阻塞,输入流需要一个单独的线程。

继续,由于委托不能是静态方法,因此您必须复制或至少同步使用缓冲区以返回结果。请记住,每个线程都有自己的栈,但都可以访问相同的堆。

NSStreamDelegate是一个允许您指定谁将管理流事件的接口。因此,允许您拆分流处理和其事件处理的编程。正如您可以将委托视为指向函数的指针一样,您必须确保它们存在于运行时,这就是为什么它们通常与协议一起使用和定义的原因。但是调用委托的方法并不意味着您调用另一个线程的函数。您只是将参数/对象应用于另一个将/必须存在于运行时的对象/类的方法。

苹果的NSThread类和NSRunLoop类使其易于使用,但会引起混淆,因为运行循环不同于线程。 每个线程都可以有一个运行循环,该循环至少循环一次,并在没有其他任务时立即返回。使用[NSRunLoop currentRunLoop],您正在请求所在线程的运行循环,而不是创建另一个线程。 因此,如果从同一线程两次调用完全相同的运行循环,则结果是在同一线程中执行工作。遵循这意味着如果一个部分阻塞,则您正在阻塞线程,因此同一线程中的其他部分也会等待。(在上一句话中,您可以将单词线程替换为运行循环,它仍然是相同的,它会阻塞线程)

通常,在输入和输出流应同时工作时,涉及多个套接字端口。 NSInputStream只读,而NSOutputStream只写。 由于来自远程发送方的数据和定时给出的可能出现意外结果的性质,因此明智的做法是为输入流提供自己的线程。您负责定义调用一次后运行循环(或线程)是否应保持活动状态。

这样做,您的输出流就在另一个线程中,在您请求其当前运行循环的线程中。 您无法创建或管理运行循环,只能请求它,因为每个线程都有一个,如果没有-则会为您创建一个。

在iOS上,你有很多解决方案可用于实现你的个人设计模式,以进行同时输入和输出流。你可以使用NSThread,也可以使用-performSelectorInBackground:SEL withObject:(nullable Id),它实际上是在NSThread.h中定义的NSObject的扩展。但是,最后一个不允许你定义特殊的运行模式。
如果您不想为自己的需求创建一个定制的NSThread子类,这里提供了一个简单的解决方案,可能也适用于您。 iOS how can i perform multiple NSInputStream 这里的[NSRunLoop currentRunloop]是分离的线程的运行循环。也可以使用块来分离新线程。
id<NSStreamDelegate> streamDelegate = //object that conforms to the protocol
[NSThread detachNewThreadWithBlock:^(void){
    NSInputStream *inputStream;
    [inputStream setDelegate:streamDelegate];
    // define your stream here
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                   forMode:NSRunLoopCommonModes];
    [inputStream open];
}];`

PS:@Ping提供的示例是用于交互两个流的事件开关,它并不会使得流的输入和输出同时进行。但是,你可以在两个流及其事件上使用这个示例,无论它们是否同时发生,这是典型的NSStreamDelegate操作。


请不要忘记在线程退出时进行定义(通常在线程内部)。 - Ol Sen
这是一个很好的答案,提供了很多相关信息,但我用更好的方法解决了问题(“如何让它们在自己的线程中运行?”)。 - user1725145

-3
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
            [self _readData];
            break;
        case NSStreamEventHasSpaceAvailable:
            [self _writeData];
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
        default:
            break;
    }
}

6
仅仅编写代码并不是答案。你必须对它进行解释。即使只是简单地解释一下为什么要做你正在做的事情,也是非常重要的。 - Fogmeister
这个答案不仅仅是复制粘贴的代码,它在互联网上随处可见,并且并没有回答问题。 - user1725145

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接