将用作NSInputStream的缓冲NSOutputStream?

14
我有一个消费者类,它以NSInputStream作为参数进行异步处理,我希望推送来自生产者类的数据,该类需要提供NSOutputStream作为其输出源。现在,我该如何设置一个缓冲(或透明)流,使其同时作为生产者的输出流和我的消费者类的NSInputStream?
我尝试过使用NSOutputStream中的+outputStreamToMemory和+outputStreamToBuffer:capacity:方法,但并没有弄清楚如何将其用作NSInputSource的输入。
我考虑设置一个中间人类来保存实际的缓冲区,然后创建两个子类(一个用于每个NSInput/OutputStream),这些子类持有对该缓冲类的引用,并让这些子类将大多数调用委托给该类,例如输出子类方法hasSpaceAvailable、write:maxLength:,以及输入方面的hasBytesAvailable、read:maxLength:等等。
欢迎就如何处理此情况给出任何提示。谢谢。

这是一个很好的问题,我也在努力寻找答案!到目前为止,我想到的方法是使用文件作为中间人(这似乎不是很高效)。希望有人能找到更好的解决方案。 - Nick
我在苹果开发者论坛中获得了一些提示,指向了CFStreamCreateBoundPair。在SimpleURLConnections示例项目中有一些示例代码。您还可以查看https://devforums.apple.com/message/258868#258868。但是在那之后我没有深入研究过它。 - cahlbin
4个回答

11

一种实现方法是使用苹果开发者网站上的示例代码。 SimpleURLConnection示例

这就是如何实现它,可以在PostController.m代码中看到。

@interface NSStream (BoundPairAdditions)
+ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr outputStream:(NSOutputStream **)outputStreamPtr bufferSize:(NSUInteger)bufferSize;
@end

@implementation NSStream (BoundPairAdditions)

+ (void)createBoundInputStream:(NSInputStream **)inputStreamPtr outputStream:(NSOutputStream **)outputStreamPtr bufferSize:(NSUInteger)bufferSize
{
    CFReadStreamRef     readStream;
    CFWriteStreamRef    writeStream;

    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );

    readStream = NULL;
    writeStream = NULL;

    CFStreamCreateBoundPair(
        NULL, 
        ((inputStreamPtr  != nil) ? &readStream : NULL),
        ((outputStreamPtr != nil) ? &writeStream : NULL), 
        (CFIndex) bufferSize);

    if (inputStreamPtr != NULL) {
        *inputStreamPtr  = [NSMakeCollectable(readStream) autorelease];
    }
    if (outputStreamPtr != NULL) {
        *outputStreamPtr = [NSMakeCollectable(writeStream) autorelease];
    }
}
@end

基本上,您可以使用缓冲区将两个流的末尾连接在一起。


当调用dataReceived的handleEvent方法时,该方法是否会在同一方法堆栈中触发(即立即执行),还是会在另一个事件中触发? - Mats Stijlaart
1
我使用这种方法创建了一对流。当NSOutputStream接收到推送的数据时,它会得到所有的handleEvent委托回调,但是我的NSInputStream却没有任何委托回调。有什么想法吗? - elsurudo
没事了。我忘记先打开输入流,然后在我的运行循环中安排它。谢谢! - elsurudo
CFStreamCreateBoundPair被认为比尝试子类化NSInputStream并覆盖私有API更安全[1]。然而,AFNetworking在这方面存在问题,因此,目前来说,覆盖私有API是最好的解决方案[2]。[1]: osdir.com/ml/general/2012-02/msg08594.html [2]: github.com/AFNetworking/AFNetworking/pull/1044 - Heath Borders
@JugsteR,我一直在苦苦寻找“NSInputStream **”在Swift中的等效方式。指针在Swift中是不鼓励使用的,所以我不确定是否有替代方法? - jrisberg
显示剩余7条评论

1

您可能想考虑从NSInputStream进行子类化,并在您的新类中包装源流,以便缓冲和/或修改通过的字节。

我发现这样做胜过绑定套接字方法的主要原因是支持寻址。基于文件的NSInputStream使用流属性在文件内搜索,而不进行子类化就很难实现。

这种方法的问题在于似乎无法为您的子类使用无缝桥接技术,但是如果您需要,有一篇非常好的文章也会为您提供一个模板子类:

http://bjhomer.blogspot.co.uk/2011/04/subclassing-nsinputstream.html

我使用两种方法都实现了缓冲区解决方案 - 尽管我在子类方法上遇到的另一个问题是,你需要小心地适当地向监听器发送事件 - 例如,当你的源流向你发送EOF事件时,你不会将其传递给消费者,直到他们清空缓冲区 - 所以那里有一些麻烦要做。
此外 - 您可能需要确保客户端在主运行循环之外进行阅读(我使用了Grand Central Dispatch使其工作) - 因为您在子类中进行的任何观察 - 在源流上 - 否则将与消费者发生冲突。尽管您似乎可以选择任何运行循环来观察流,但只有主循环才起作用。
所以总体而言,除非您需要支持寻址或对成对流方法特别反感,否则我建议使用成对流。

0

这里有一个已经实现的类,可以完全满足你的需求

BufferOutputStreamToInputStream

// initialize
self.bufferWriter = [[BufferOutputStreamToInputStream alloc] init];
[self.bufferWriter openOutputStream];

// later you want to set the delegate of the inputStream and shedule it in runloop
// remember, you are responsible for the inputStream, the outputStream is taken care off;)
self.bufferWriter.inputStream.delegate = self;
[self.bufferWriter.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.bufferWriter.inputStream open]

// fill with data when desired on some event      
[self.bufferWriter addDataToBuffer:someData];

0

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