Cocoa Stream 系列3--NSOutputStream的使用

来源:互联网 发布:量子力学知乎 编辑:程序博客网 时间:2024/06/08 14:15

Writing To Output Streams--输出数据流

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/WritingOutputStreams.html#//apple_ref/doc/uid/20002274-BAJCABBC
使用输出数据流需要以下步骤:
1.创建并初始化一个NSOutputStream ,并需要有一个写入数据的仓库。同样设置代理。
2.将数据流对象安排到 run loop中,并开启
3.处理数据流汇报给代理的事件
4.如果数据流对象向内存中写入数据,通过NSStreamDataWrittenToMemoryStreamKey属性获取data.
5.当没有更多数据写入的时候,将数据流对象处理掉
以下会详细介绍每一步:

这个示例中,展示了将流对象安排到run loop中,并设置了代理来处理相关的事件,你可以使用polling,取代run-loop。但是run loop是比较推荐的方法,这也是本文档特别讲解它的原因。

Preparing the Stream Object

开始使NSOutputStream 对象之前,你必须有一个写入数据的目标地址,目标地址可以是一个文件,一个缓存区buffer,应用内存或者网络套接字接口。

提示:从网络套接字接口初始化输出数据流对象的方法和其他目的地方法不同,本篇文章中没有介绍,想了解更多关于网络连接中NSOutputStream 的初始化方法,请参考“Setting Up Socket Streams.”

初始化或者类方法都可以从一个文件,缓存区,或者内存创建一个NSOutputStream 对象

Listing 1  创建并初始化一个 NSOutputStream 对象 

- (void)createOutputStream {
    NSLog(@"Creating and opening NSOutputStream...");
    // oStream is an instance variable
    oStream = [[NSOutputStream alloc] initToMemory];
    [oStream setDelegate:self];
    [oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
        forMode:NSDefaultRunLoopMode];
    [oStream open];
}

As the code in Listing 1 shows, after you create the object you should set the delegate (more often than not to self). The delegate receivesstream:handleEvent: messages from the NSOutputStream object when that object has stream-related events to report, such as when the stream has space for bytes.

如示例所示,创建完对象,需要设置代理,当对象安排到run loop中,并有数据流相关的事件进行汇报的时候,代理接受到stream:handleEvent: 信息 ,比如当有空间存储数据时。

在开启数据流之前,需要发送scheduleInRunLoop:forMode:信息给数据流对象,以安排其接受数据流事件,通过这个,你可以帮助代理实现避免堵塞当没有数据进行读取的时候。当数据流在其他线程发生时,确保将数据流对象安排在对应的线程的run loop 中。你绝对不能从其他不属于当前线程的run loop获取数据流。最后,发送open 信息给数据流对象,开启数据流的写入。

Handling Stream Events

数据流开启后,你可以获取到它的状态,是否有可写入的空间,错误信息和以下信息:

  • streamStatus

  • HasSpaceAvailable

  • streamError

返回数据流当前的状态是一个NSStreamStatus常量,是否开启,正在读取中,读取到文件的最后等等。返回的错误是一个  NSError 对象,包含错误信息。

更重要的是,一旦数据流开启,它一直发送 stream:handleEvent:信息给它的代理(只要代理保持写入数据),这些信息包含一个参数,是一个 NSStreamEvent常量 ,标识着事件的类型。对于NSOutputStream 对象,常用的事件类型为  are NSStreamEventOpenCompleted, NSStreamEventHasSpaceAvailable, and NSStreamEventEndEncountered. 代理最感兴趣的是NSStreamEventHasSpaceAvailable 事件.

 示例2 介绍了一个很好的处理该事件的方法

Listing 2  处理space-available事件

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
    switch(eventCode) {
        case NSStreamEventHasSpaceAvailable:
        {
            uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
            readBytes += byteIndex; // instance variable to move pointer
            int data_len = [_data length];
            unsigned int len = ((data_len - byteIndex >= 1024) ?
                1024 : (data_len-byteIndex));
            uint8_t buf[len];
            (void)memcpy(buf, readBytes, len);
            len = [stream write:(const uint8_t *)buf maxLength:len];
            byteIndex += len;
            break;
        }
        // continued ...
    }
}

. If the constant is , the delegate gets the bytes held by a and advances the pointer for the current write operation.

在这个代理方法的实现中,stream:handleEvent:,使用了switch 语句,区分NSStreamEvent 变量,如果变量是 NSStreamEventHasBytesAvailable,代理通过 NSMutableData object (_data)对象读取数据流,并将指针放到当前位置,然后决定写操作的二进制流大小,声明一个同样size的 buffer ,然后复制同样的data到这个缓冲区, 然后告诉代理调用 output-stream 对象的write:maxLength: 方法将缓存区的内容写入到输出数据流中,最后增加readBytes指针的位置到下一个操作。 

如果代理接收到NSStreamEventHasSpaceAvailable 事件,并且没有写入任何信息,当前run loop将不会接收更多的NSStreamEventHasSpaceAvailable 事件,直到NSOutputStream 对象接收到二进制流,这时,run loop会重启接收NSStreamEventHasSpaceAvailable 事件,如果有类似的场合在你的应用中,当接收到NSStreamEventHasSpaceAvailable 事件不进行写操作时,你可以设置一个标记, 然后当你有二进制需要进行写入时,你检查这个标记,然后直接向数据流对象中写入数据。

这里没有统一的规定关于一次性读取多少字节的数据,甚至可以一次性读取全部的数据,这取决于数据流的大小,设备和套接字接口的特点。最好的方法是使用合理的缓冲区大小,比如512字节,1M,或者4M.

如果数据流对象在处理数据的时候发生错误,会停止处理,并告知代理aNSStreamEventErrorOccurred.代理需要按照“Handling Stream Errors.”处理错误。

Disposing of the Stream Object

Listing 3 illustrates how you might do all of these things.

NSOutputStream 对象结束数据写入的时候,需要发送给代理a NSStreamEventEndEncountered 事件通过代理方法stream:handleEvent:发送信息. 代理需要按照创建代理相反的方法处理数据流对象。换句话说,就是关闭数据流,从 run loop中移除,最后 release 。更多的是,如果NSOutputStream对象的目标存储地址是应用中的内存,(也就是说你使用initToMemory 方法或者outputStreamToMemory方法创建的NSOutputStream),你可能需要重新提取内存中的数据。

Listing 3  Closing and releasing the NSInputStream object

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
    switch(eventCode) {
        case NSStreamEventEndEncountered:
        {
            NSData *newData = [oStream propertyForKey:
                NSStreamDataWrittenToMemoryStreamKey];
            if (!newData) {
                NSLog(@"No data written to memory!");
            } else {
                [self processData:newData];
            }
            [stream close];
            [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
                forMode:NSDefaultRunLoopMode];
            [stream release];
            oStream = nil; // oStream is instance variable
            break;
        }
        // continued ...
    }
}

你通过向NSOutputStream 发送propertyForKey:信息,将stream data 写入内存中,使用NSStreamDataWrittenToMemoryStreamKey,数据流对象返回一个 NSData 对象.

0 0