[ios]流操作 【转】
http://blog.csdn.net/caryaliu/article/details/7658536
向输出流写数据
使用NSOutputStream实例需要以下几个步骤:
1,使用存储写入数据的存储库创建和初始化一个NSOutputSteam实例,并且设置它的delegate。
2,将这个流对象布置在一个runloop上并且open the stream。
3,处理流对象向其delegate发送的事件消息。
4,如果流对象向内存中写入了数据,那么可以通过使用NSStreamDataWrittenToMemoryStreamKey属性获取数据。
5,当没有数据可供写入时,清理流对象。
一,使用流对象的准备工作
使用NSOutputStream对象之前你必须指定数据写入的流的目标位置,输出流对象的目标位置可以是file,C buffer, application memory,network socket。
NSOutputStream的初始化方法和工厂方法可以使用a
file,a buffer, memory来创建和初始化实例,下面的代码初始化了一个NSOutputStream实例,用来向 application memory 写入数据。
- - (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];
- }
上面的代码显示,在你初始化一个NSOutputStream对象之后应该设置它的delegate(通常是self),当流对象有 有空间可供数据写入 之类的与流有关的事件消息发送时,delegate会收到从NSOutputStream对象发送来的消息。
当你在open the stream对象之前,向流对象发送scheduleInRunLoop:forMode: 消息使其在一个runloop上可以接收到stream events,这样,当流对象不能接收更多数据的时候,可以使delegate避免阻塞。当streaming发生在另外一个线程时,你必须将流对象布置 在那个线程的run loop上,You should never attempt to access a scheduled stream from a thread different than the one owning the stream’s run loop. 最后 open the stream 开始数据向 NSOutputStream对象传送。
二,处理 Stream Events
当你向流对象发送open消息之后,你可以通过以下消息获取到流对象的状态,比如说当前是否有空间可供数据写入以及其他错误信息的属性。
-
streamStatus
-
hasSpaceAvailable
-
streamError
返回的状态是NSStreamStatus常量,它指示流当前的状态是opening,writing,at the end of the stream等等,返回的错误是NSError对象,它封装的是所有错误的信息。
重 要的是,一旦open the stream,只要delegate持续想流对象写入数据,流对象就是一直向其delegate发送stream:handleEvent:消息,直到到 达了流的末尾。这些消息中包含一个NSStreamEvent常量参数来指示事件的类型。对于一个NSOutputStream对象,最常见的事件类型是 NSStreamEventOpenCompleted,NSStreamEventHasSpaceAvailable,NSStreamEventEndEncountered,delegate 通常对NSStreamEventHasSpaceAvaliable事件最感兴趣。下面的代码就是处理 NSStreamEventHasSpaceAvaliable事件的一种方法:
- - (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 ...
- }
- }
在stream:handleEvent:的实现中使用switch语句来判别NSStreamEvent常量,当这个常量是NSStreamEventHasSpacesAvailable的时候,delegate从NSMutableData 对象_data中获取数据,并且将其指针转化为适合当前操作的类型u_int8.下一步计算即将进行写操作的字节数(是1024还是所有剩余的字节数), 声明一段相应大小的buffer,向该buffer写入相应大小的数据,然后delegate调用流对象write:maxLength:方法将buffer中的数据置入output stream中,最后更新byteIndex用于下一次的读取操作。
如果delegate收到NSStreamEventHasSpacesAvailable事件消息但是没有向stream里写入任何数据,它不会从runloop再接收到space-available的事件消息直到NSOutputStream对象接收到数据,这样由于space-available事件该run loop会重新启动。如果这种情况很有可能在你的程序设计中出现,在收到NSStreamEventHasSpaceAvailable消息并且没有向该stream中写入数据时可以在delegate中设置一个标志位flag,之后,当存在更多的数据需要写入时,先检查该标志位,如果该标志位被设置,那么直接向output-stream实例写入数据。
对于一次向output-stream实例中写入多少数据没有严格的限定,一般情况下使用一些合理值,如512Bytes,1kB,4kB(一个页面大小)。
当在向stream中写数据时NSOutputStream对象发生错误,它会停止streaming并且使用NSStreamEventErrorOccurred消息通知其delegate。
三,清理 Stream Object
当一个NSOutputStream对象结束向一个output stream写入数据,它通过stream:handleEvent:消息向delegate发送NSStreamEventEndEncountered事件消息,这个时候delegate应该清理 stream object,先关闭该stream object,从run loop中移除,释放该stream object。此外,如果NSOutputStream对象的目的存储库是application memory(也就是,你通过initToMemory方法或者其工厂方法outputStreamToMemory创建的该对象),现在就可以从内存中获取数据了。下面的代码实现的清理 stream 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:消息获取从流向内存中写入的数据,设定key的值为NSStreamDataWrittenToMemoryStreamKey,该stream object将数据返回到一个NSData对象中。
输入流里读入数据
ios cocoa 编程,从NSInputStream中读入数据包括几个步骤:
1.从数据源创建和初始化一个NSInputStream实例
2.将输入流对象配置到一个run loop,open the stream
3. 通过流对象的delegate函数处理事件
4. 当所有数据读完,进行流对象的内存处理
一,使用流对象的准备工作
在使用NSInputStream对象之前你必须有流的数据源,数据源的类型可以是文件,NSData对象,或者一个网络套接字。
NSInputStream的初始化函数和工厂方法可以从NSData和文件创建和初始化一个NSInputStream的实例。下面的例子是从文件创建一个NSInputStream的实例:
- - (void)setUpStreamForFile:(NSString *)path {
- // iStream is NSInputStream instance variable
- iStream = [[NSInputStream alloc] initWithFileAtPath:path];
- [iStream setDelegate:self];
- [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
- forMode:NSDefaultRunLoopMode];
- [iStream open];
- }
上面的例子显示,当你创建对象之后你应该设置其delegate。当把NSInputStream对象配置到一个run loop,并且有与流相关的事件(例如流中有可读数据)发生时,该对象会收到stream:handleEvent:消息。
在你open stream之前,给流对象发送一个scheduleInRunLoop:forMode: 消息,来将该对象配置到一个run loop接收stream events。这样,当流中没有数据可读时可以避免delegate阻塞。如果流是发生在另一个线程,你需要确认该流对象是配置在那个线程的run loop中。你不应该尝试从一个除了包含该流对象的run loop的线程的其他线程中对流进行操作。最后,对NSInputStream对象发送open消息开始对输入数据的流操作。
二,处理Stream Events
当你对一个流对象发送open消息之后,你可以查找到它的当前状态。通过下面的消息可以知道流对象中是否有数据可读,以及任何错误的属性:
-
streamStatus
-
hasBytesAvailable
-
streamError
返回的状态是一个NSStreamStatus常量,它可以指示流对象是处于opening,reading,或者at the end of the stream等等。返回的错误是一个NSError对象,它封装了可能发生的所有错误信息。
重要的是,一旦
open 流对象,流对象会一直向其delegate发送stream:handleEvent:
消息直到到达了流对象的末尾。这些消息的参数中包含一个指示流事件类型的NSStreamEvent常量。对NSInputStream对象而言,最常用的事件类型是NSStreamEventOpenCompleted,NSStreamEventHasBytesAvailable,NSStreamEventEndEncountered。我们尤其感兴趣的应该是NSStreamEventHasBytesAvailable事件。下面的例子就是一个处理NSStreamEventHasBytesAvailable事件的好的方法:
- - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
- switch(eventCode) {
- case NSStreamEventHasBytesAvailable:
- {
- if(!_data) {
- _data = [[NSMutableData data] retain];
- }
- uint8_t buf[1024];
- unsigned int len = 0;
- len = [(NSInputStream *)stream read:buf maxLength:1024];
- if(len) {
- [_data appendBytes:(const void *)buf length:len];
- // bytesRead is an instance variable of type NSNumber.
- [bytesRead setIntValue:[bytesRead intValue]+len];
- } else {
- NSLog(@"no buffer!");
- }
- break;
- }
- // continued
- }
stream:handleEvent: 函数使用switch语句来判别NSStreamEvent常量,当这个常量是MSStreamEventHasBytesAvailable的时候,delegate函数会lazy create 一个NSMutableData对象_data来接收读取的数据。然后声明一个大小为1024的uint8_t类型数组buf,调用read:maxLength:函数从stream中读取指定大小的数据到buf中,如果读取成功,delegate将会将读取到的数据添加到NSMutableData对象_data中,并且更新总的读取到的数据bytesRead.
至于一次从stream中读取多大的数据,一般来说,使用一些常用的数据大小规格,比如说512Bytes,1kB,4kB(一个页面大小)。
三,处理stream object
当NSInputStream对象到达steam的末尾的时候,它会向stream:handleEvent:函数发送一个NSStreamEventEndEncountered事件类型常量,delegate函数应该做出与准备使用流对象相反的操作,也就是说,需要关闭流对象,从run loop中移除,最终释放流对象。如下面的代码所示:
- - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
- {
- switch(eventCode) {
- case NSStreamEventEndEncountered:
- {
- [stream close];
- [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
- forMode:NSDefaultRunLoopMode];
- [stream release];
- stream = nil; // stream is ivar, so reinit it
- break;
- }
- // continued ...
- }
- }
-
通常情况下,特别是与sockets相关联时,streams 会遇到错误从而不能进一步处理stream data。一般情况下,错误提示了在流的一端缺失了东西,比如说远程主机的crash,正在使用的文件被删除等等。在此情况下,客户端能够做的就是将这些 错误提示给用户,尽管一个stream object在上报错误之后,在它关闭之前仍然可以查询它的状态,但是它不能再用于写或者读操作。
当错误发生时,NSStream和NSOutputStream类通过以下几种方式发起通知:
1,如果stream object布置在一个run loop上,那么该对象通过stream:handleEvent:消息向其delegate发送NSStreamEventErrorOccurred事件发起通知
2,任何时候客户端可以向stream object发送streamStatus消息查询是否会返回NSStreamStatusError
3,如果你尝试通过write:maxLength:消息向NSOutputStream对象写数据时,该消息返回值 -1,这时发生一个写错误
一旦你检查到stream object发生错误,你可以向stream object发送streamError消息获得更加详细的信息(是一个NSError对象),然后将该错误信息通知给用户。下面的代码是布置在一个run loop上的stream object的delegate处理错误的方法:
- - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
- NSLog(@"stream:handleEvent: is invoked...");
- switch(eventCode) {
- case NSStreamEventErrorOccurred:
- {
- NSError *theError = [stream streamError];
- NSAlert *theAlert = [[NSAlert alloc] init]; // modal delegate releases
- [theAlert setMessageText:@"Error reading stream!"];
- [theAlert setInformativeText:[NSString stringWithFormat:@"Error %i: %@",
- [theError code], [theError localizedDescription]]];
- [theAlert addButtonWithTitle:@"OK"];
- [theAlert beginSheetModalForWindow:[NSApp mainWindow]
- modalDelegate:self
- didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
- contextInfo:nil];
- [stream close];
- [stream release];
- break;
- }
- // continued ....
- }
- }
- 处理流错误