[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 写入数据。

 

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


上面的代码显示,在你初始化一个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事件的一种方法:

 

  1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode  
  2. {  
  3.     switch(eventCode) {  
  4.         case NSStreamEventHasSpaceAvailable:  
  5.         {  
  6.             uint8_t *readBytes = (uint8_t *)[_data mutableBytes];  
  7.             readBytes += byteIndex; // instance variable to move pointer  
  8.             int data_len = [_data length];  
  9.             unsigned int len = ((data_len - byteIndex >= 1024) ?  
  10.                 1024 : (data_len-byteIndex));  
  11.             uint8_t buf[len];  
  12.             (void)memcpy(buf, readBytes, len);  
  13.             len = [stream write:(const uint8_t *)buf maxLength:len];  
  14.             byteIndex += len;  
  15.             break;  
  16.         }  
  17.         // continued ...  
  18.     }  
  19. }  

 

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的工作:

 

  1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode  
  2. {  
  3.     switch(eventCode) {  
  4.         case NSStreamEventEndEncountered:  
  5.         {  
  6.             NSData *newData = [oStream propertyForKey:  
  7.                 NSStreamDataWrittenToMemoryStreamKey];  
  8.             if (!newData) {  
  9.                 NSLog(@"No data written to memory!");  
  10.             } else {  
  11.                 [self processData:newData];  
  12.             }  
  13.             [stream close];  
  14.             [stream removeFromRunLoop:[NSRunLoop currentRunLoop]  
  15.                 forMode:NSDefaultRunLoopMode];  
  16.             [stream release];  
  17.             oStream = nil; // oStream is instance variable  
  18.             break;  
  19.         }  
  20.         // continued ...  
  21.     }  
  22. }  

通过向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的实例:

 

  1. - (void)setUpStreamForFile:(NSString *)path {  
  2.     // iStream is NSInputStream instance variable  
  3.     iStream = [[NSInputStream alloc] initWithFileAtPath:path];  
  4.     [iStream setDelegate:self];  
  5.     [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]  
  6.         forMode:NSDefaultRunLoopMode];  
  7.     [iStream open];  
  8. }  

上面的例子显示,当你创建对象之后你应该设置其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事件的好的方法:

 

  1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {  
  2.    
  3.     switch(eventCode) {  
  4.         case NSStreamEventHasBytesAvailable:  
  5.         {  
  6.             if(!_data) {  
  7.                 _data = [[NSMutableData data] retain];  
  8.             }  
  9.             uint8_t buf[1024];  
  10.             unsigned int len = 0;  
  11.             len = [(NSInputStream *)stream read:buf maxLength:1024];  
  12.             if(len) {  
  13.                 [_data appendBytes:(const void *)buf length:len];  
  14.                 // bytesRead is an instance variable of type NSNumber.  
  15.                 [bytesRead setIntValue:[bytesRead intValue]+len];  
  16.             } else {  
  17.                 NSLog(@"no buffer!");  
  18.             }  
  19.             break;  
  20.         }  
  21.         // continued  
  22. }  

 

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中移除,最终释放流对象。如下面的代码所示:

 

    1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode  
    2. {  
    3.     switch(eventCode) {  
    4.         case NSStreamEventEndEncountered:  
    5.         {  
    6.             [stream close];  
    7.             [stream removeFromRunLoop:[NSRunLoop currentRunLoop]  
    8.                 forMode:NSDefaultRunLoopMode];  
    9.             [stream release];  
    10.             stream = nil; // stream is ivar, so reinit it  
    11.             break;  
    12.         }  
    13.         // continued ...  
    14.     }  

    15. 通常情况下,特别是与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处理错误的方法:

       

      1. - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {  
      2.     NSLog(@"stream:handleEvent: is invoked...");  
      3.    
      4.     switch(eventCode) {  
      5.         case NSStreamEventErrorOccurred:  
      6.         {  
      7.             NSError *theError = [stream streamError];  
      8.             NSAlert *theAlert = [[NSAlert alloc] init]; // modal delegate releases  
      9.             [theAlert setMessageText:@"Error reading stream!"];  
      10.             [theAlert setInformativeText:[NSString stringWithFormat:@"Error %i: %@",  
      11.                 [theError code], [theError localizedDescription]]];  
      12.             [theAlert addButtonWithTitle:@"OK"];  
      13.             [theAlert beginSheetModalForWindow:[NSApp mainWindow]  
      14.                 modalDelegate:self  
      15.                 didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)  
      16.                 contextInfo:nil];  
      17.             [stream close];  
      18.             [stream release];  
      19.             break;  
      20.         }  
      21.         // continued ....  
      22.     }  
      23. }
      24. 处理流错误
      对于某些错误,除了通知用户之后你可以做更多的工作。比如,如果你在进行socket连接的时候设置了SSL 安全等级,但是远程的主机没有设定,该stream object会发送一个错误,你可以释放之前的stream object并且创建一个新的不安全的套接字连接。

 

posted @ 2013-04-09 00:31  金建彤  阅读(665)  评论(0编辑  收藏  举报