NSStream和套接字,NSStreamDelegate方法未被调用

12

我已经按照设置套接字流指南,在我的类中有效地复制了该代码。但无论我尝试什么,委托方法似乎都没有被调用。

在头文件中,我有(基本上):

@interface myClass : NSObject <NSStreamDelegate> {
    NSInputStream *inputStream;
    NSOutputStream *outputStream;
}
- (void)connect;
@end;

connect方法:

- (void)connect {
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)@"host.example.com", 1234, &readStream, &writeStream);

    inputStream = (NSInputStream *)readStream;
    outputStream = (NSOutputStream *)writeStream;
    [inputStream setDelegate:self];
    [outputStream setDelegate:self];
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [inputStream open];
    [outputStream open];
}

尝试过使用CFStreamCreatePairWithSocketToCFHost()[NSStream getStreamsToHost:port:inputStream:outputStream:,结果完全相同。

我在connect方法的开头设置了断点,逐行执行过程并检查了每个指针,都是有效的且似乎指向了正确的对象。

在GDB中,在调用setDelegate之后,打印po [inputStream delegate]可以按预期显示为<myClass: 0x136380>,因此已正确设置代理。

我真的想不出为什么它拒绝在我的类上调用stream:handleEvent:方法:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    NSLog(@"got an event");
}

希望我错过了非常简单和明显的东西,第二个人能够发现我的错误。

提前感谢那些有耐心并花时间阅读到这里的人!

2个回答

29

在这样的行中:

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

我将[NSRunLoop currentRunLoop]改为了[NSRunLoop mainRunLoop]

编辑于2011-05-30:

这样做不起作用的原因是我在后台线程中通过+[NSThread detachNewThreadSelector:toTarget:withObject:]设置了socket。

这种方式创建了一个新的run loop,阅读run loop开发人员文档后我发现需要手动告诉NSRunLoop来运行。

在与主线程相同的run loop中运行它对性能没有问题,尽管我通过编写包装器类并在后台线程上运行所有网络I/O来挤出了更多的性能。


@user102008 编辑了我的回复以进行解释。 - arrtchiu
1
你能贴下结尾代码吗?由于某种原因,我将 [NSRunLoop currentRunLoop] 改为 [NSRunLoop mainRunLoop] 后,连接在启动后不久就失败了。 - thebiglebowski11
你做得很好!将我的套接字客户端从主线程中移除后,我停止了对输入流的监听(尽管我的输出流仍在工作,因为我可以在套接字服务器上进行监视)。我只是改变了输入流的运行循环,然后就完成了!点赞 :) - tony gil
你的问题和回答都帮助我知道该去哪里,但我不确定如何使用简单按钮开始连接? - Floris497
非常感谢。我在使用Monotouch并在任务中打开套接字时遇到了同样的问题。使用主循环而不是当前线程(任务的线程)解决了这个问题。 - Andreas Paulsson
一年后,这个答案再次帮助了我。有趣的是读到评论时发现你之前也评论过这个答案! - Floris497

9

只有当在0号线程上没有阻塞工作时,该解决方案才能起作用。这通常是可以接受的,但更好的解决方案是创建一个新线程(例如使用类方法按需创建线程),然后将其排队在该线程上。

+ (NSThread *)networkThread {
    static NSThread *networkThread = nil;
    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{
        networkThread =
             [[NSThread alloc] initWithTarget:self
                                     selector:@selector(networkThreadMain:)
                                       object:nil];
        [networkThread start];
    });

    return networkThread;
}

+ (void)networkThreadMain:(id)unused {
    do {
        @autoreleasepool {
            [[NSRunLoop currentRunLoop] run];
        }
    } while (YES);
}

- (void)scheduleInCurrentThread
{
    [inputstream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSRunLoopCommonModes];
}

通过这个功能,您可以使用以下命令安排输入流的时间:

[self performSelector:@selector(scheduleInCurrentThread)
             onThread:[[self class] networkThread]
           withObject:nil
        waitUntilDone:YES];

这将使您能够轻松运行网络操作,不再担心死锁问题。

谢谢你的建议。我有另一个相关的问题。当流正在被写入时,用户可能会将应用程序移至后台。我希望即使在后台也能保持流运行。我尝试使用beginBackgroundTask...来实现这一点,但无论我做什么,当应用程序在后台时,我都无法使我的图片上传工作。你认为这可能吗? - Sachin Palewar
我们的教程应用程序可以实现这一点。如果您仍在寻找帮助,您可能想要查看https://parse.com/tutorials/anypic#post上的内容。请注意“保存PFObject”的部分。虽然我们的框架将其隐藏起来,但后台任务中的部分会进行网络调用。 - Thomas Bouldin
2
这段代码是否定期清空自动释放池?-[NSRunLoop run]的文档说明它会重复调用runMode:beforeDate:。这意味着在这些调用之间没有机会清空自动释放池。 - user102008

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