转自:http://southpeak.github.io/blog/2014/07/17/ioszhong-liu-stream-de-shi-yong/

流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的。流是在通信路径中串行传输的连续的比特位序列。从编码的角度来看,流是单向的,因此流可以是输入流或输出流。除了基于文件的流外,其它形式的流都是不可查找的,这些流的数据一旦消耗完后,就无法从流对象中再次获取。

在Cocoa中包含三个与流相关的类:NSStream、NSInputStream和NSOutputStream。NSStream是一个抽象基类,定义了所有流对象的基础接口和属性。NSInputStream和NSOutputStream继承自NSStream,实现了输入流和输出流的默认行为。下图描述了流的应用场景:



从图中看,NSInputStream可以从文件、socket和NSData对象中获取数据;NSOutputStream可以将数据写入文件、socket、内存缓存和NSData对象中。这三处类主要处理一些比较底层的任务。

流对象有一些相关的属性。大部分属性是用于处理网络安全和配置的,这些属性统称为SSL和SOCKS代理信息。两个比较重要的属性是:

  1. NSStreamDataWrittenToMemoryStreamKey:允许输出流查询写入到内存的数据
    NSStreamFileCurrentOffsetKey:允许操作基于文件的流的读写位置

可以给流对象指定一个代理对象。如果没有指定,则流对象作为自己的代理。流对象调用唯一的代理方法stream:handleEvent:来处理流相关的事件:

  1. 对于输入流来说,是有可用的数据可读取事件。我们可以使用read:maxLength:方法从流中获取数据
    对于输出流来说,是准备好写入的数据事件。我们可以使用write:maxLength:方法将数据写入流

Cocoa中的流对象与Core Foundation中的流对象是对应的。我们可以通过toll-free桥接方法来进行相互转换。NSStream、NSInputStream和NSOutputStream分别对应CFStream、CFReadStream和CFWriteStream。但这两者间不是完全一样的。Core Foundation一般使用回调函数来处理数据。另外我们可以子类化NSStream、NSInputStream和NSOutputStream,来自定义一些属性和行为,而Core Foundation中的流对象则无法进行扩展。

上面主要介绍了iOS中流的一些基本概念,我们下面将介绍流的具体使用,首先看看如何从流中读取数据。

从输入流中读取数据

从一个NSInputStream流中读取数据主要包括以下几个步骤:

  1. 从数据源中创建和初始化一个NSInputStream实例
    将流对象放入一个run loop中并打开流
    处理流对象发送到其代理的事件
    当没有更多数据可读取时,关闭并销毁流对象。
    准备流对象

要使用一个NSInputStream,必须要有数据源。数据源可以是文件、NSData对象和网络socket。创建好后,我们设置其代理对象,并将其放入到run loop中,然后打开流。代码清单1展示了这个准备过程.

代理清单1

 

复制代码

  1. - (void)setUpStreamForFile:(NSString *)path
  2. {
  3. NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:path];
  4. inputStream.delegate = self;
  5. [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  6. [inputStream open];
  7. }

在流对象放入run loop且有流事件(有可读数据)发生时,流对象会向代理对象发送stream:handleEvent:消息。在打开流之前,我们需要调用流对象的scheduleInRunLoop:forMode:方法,这样做可以避免在没有数据可读时阻塞代理对象的操作。我们需要确保的是流对象被放入正确的run loop中,即放入流事件发生的那个线程的run loop中。

处理流事件

打开流后,我们可以使用streamStatus属性查看流的状态,用hasBytesAvailable属性检测是否有可读的数据,用streamError来查看流处理过程中产生的错误。

流一旦打开后,将会持续发送stream:handleEvent:消息给代理对象,直到流结束为止。这个消息接收一个NSStreamEvent常量作为参数,以标识事件的类型。对于NSInputStream对象,主要的事件类型包括NSStreamEventOpenCompleted、NSStreamEventHasBytesAvailable和NSStreamEventEndEncountered。通常我们会对NSStreamEventHasBytesAvailable更感兴趣。代理清单2演示了从流中获取数据的过程

代理清单2

 

复制代码

  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  2. {
  3. switch (eventCode) {
  4. case NSStreamEventHasBytesAvailable:
  5. {
  6. if (!data) {
  7. data = [NSMutableData data];
  8. }
  9. uint8_t buf[1024];
  10. unsigned int len = 0;
  11. len = [(NSInputStream *)aStream read:buf maxLength:1024];  // 读取数据
  12. if (len) {
  13. [data appendBytes:(const void *)buf length:len];
  14. }
  15. }
  16. break;
  17. }
  18. }

当NSInputStream在处理流的过程中出现错误时,它将停止流处理并产生一个NSStreamEventErrorOccurred事件给代理。我们同样的上面的代理方法中来处理这个事件。

清理流对象

当NSInputStream读取到流的结尾时,会发送一个NSStreamEventEndEncountered事件给代理,代理对象应该销毁流对象,此过程应该与创建时相对应,代码清单3演示了关闭和释放流对象的过程。

代理清单3

 

复制代码

  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  2. {
  3. switch (eventCode) {
  4. case NSStreamEventEndEncountered:
  5. {
  6. [aStream close];
  7. [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  8. aStream = nil;
  9. }
  10. break;
  11. }
  12. }

写入数据到输出流

类似于从输入流读取数据,写入数据到输出流时,需要下面几个步骤:

  1. 使用要写入的数据创建和初始化一个NSOutputStream实例,并设置代理对象
    将流对象放到run loop中并打开流
    处理流对象发送到代理对象中的事件
    如果流对象写入数据到内存,则通过请求NSStreamDataWrittenToMemoryStreamKey属性来获取数据
    当没有更多数据可供写入时,处理流对象

基本流程与输入流的读取差不多,我们主要介绍不同的地方

数据可写入的位置包括文件、C缓存、程序内存和网络socket。

  1. hasSpaceAvailable属性表示是否有空间来写入数据
    在stream:handleEvent:中主要处理NSStreamEventHasSpaceAvailable事件,并调用流的write:maxLength方法写数据。代码清单4演示了这一过程。
    如果NSOutputStream对象的目标是应用的内存时,在NSStreamEventEndEncountered事件中可能需要从内存中获取流中的数据。我们将调用NSOutputStream对象的propertyForKey:的属性,并指定key为NSStreamDataWrittenToMemoryStreamKey来获取这些数据。

代理清单4
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventHasSpaceAvailable:
        {
            uint8_t *readBytes = (uint8_t *)[data mutableBytes];
            readBytes += byteIndex;
            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 = [aStream write:(const uint_8 *)buf maxLength:len];
            byteIndex += len;
            break;
        }
    }
}

这里需要注意的是:当代理接收到NSStreamEventHasSpaceAvailable事件而没有写入任何数据到流时,代理将不再从run loop中接收该事件,直到NSOutputStream对象接收到更多数据,这时run loop会重启NSStreamEventHasSpaceAvailable事件。

流的轮循处理

在流的处理过程中,除了将流放入run loop来处理流事件外,还可以对流进行轮循处理。我们将流处理数据的过程放到一个循环中,并在循环中不断地去询问流是否有可用的数据供读取(hasBytesAvailable)或可用的空间供写入(hasSpaceAvailable)。当处理到流的结尾时,我们跳出循环结束流的操作。

具体的过程如代码清单5所示

代码清单5

 

复制代码

  1. - (void)createNewFile {
  2. NSOutputStream *oStream = [[NSOutputStream alloc] initToMemory];
  3. [oStream open];
  4. uint8_t *readBytes = (uint8_t *)[data mutableBytes];
  5. uint8_t buf[1024];
  6. int len = 1024;
  7. while (1) {
  8. if (len == 0) break;
  9. if ([oStream hasSpaceAvailable])
  10. {
  11. (void)strncpy(buf, readBytes, len);
  12. readBytes += len;
  13. if ([oStream write:(const uint8_t *)buf maxLength:len] == -1)
  14. {
  15. [self handleError:[oStream streamError]];
  16. break;
  17. }
  18. [bytesWritten setIntValue:[bytesWritten intValue]+len];
  19. len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 : [data length] - [bytesWritten intValue]);
  20. }
  21. }
  22. NSData *newData = [oStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
  23. if (!newData) {
  24. NSLog(@"No data written to memory!");
  25. } else {
  26. [self processData:newData];
  27. }
  28. [oStream close];
  29. [oStream release];
  30. oStream = nil;
  31. }

这种处理方法的问题在于它会阻塞当前线程,直到流处理结束为止,才继续进行后面的操作。而这种问题在处理网络socket流时尤为严重,我们必须等待服务端数据回来后才能继续操作。因此,通常情况下,建议使用run loop方式来处理流事件。

错误处理

当流出现错误时,会停止对流数据的处理。一个流对象在出现错误时,不能再用于读或写操作,虽然在关闭前可以查询它的状态。

NSStream和NSOutputStream类会以几种方式来告知错误的发生:

 

复制代码

  1. 如果流被放到run loop中,对象会发送一个NSStreamEventErrorOccurred事件到代理对象的stream:handleEvent:方法中
  2. 任何时候,可以调用streamStatus属性来查看是否发生错误(返回NSStreamStatusError)
  3. 如果在通过调用write:maxLength:写入数据到NSOutputStream对象时返回-1,则发生一个写错误。

一旦确定产生错误时,我们可以调用流对象的streamError属性来查看错误的详细信息。在此我们不再举例。

设置Socket流

在iOS中,NSStream类不支持连接到远程主机,幸运的是CFStream支持。前面已经说过这两者可以通过toll-free桥接来相互转换。使用CFStream时,我们可以调用CFStreamCreatePairWithSocketToHost函数并传递主机名和端口号,来获取一个CFReadStreamRef和一个CFWriteStreamRef来进行通信,然后我们可以将它们转换为NSInputStream和NSOutputStream对象来处理。

具体的处理流程我们会在后期详细讨论。

参考

Stream Programming Guide

【转】iOS中流(Stream)的使用的更多相关文章

  1. iOS socket Stream 服务器端 及 客户端 演示

    iOS socket Stream 测试环境,mac osx 10.8 一:建立服务器端 由于mac osx10.8 已经集成 python2和 Twisted,我们可以直接利用此,构建一个简单的so ...

  2. nodejs中流(stream)的理解

    nodejs的fs模块并没有提供一个copy的方法,但我们可以很容易的实现一个,比如: var source = fs.readFileSync('/path/to/source', {encodin ...

  3. C#中流Stream的使用-学习

    概念 提供字节序列的一般视图.这是一个抽象类. 子类: Derived Microsoft.JScript.COMCharStream System.Data.OracleClient.OracleB ...

  4. nodejs中stream相关资料

    nodejs中流(stream)的理解 nodejs stream 手册完整中文版本 nodejs stream详细使用介绍

  5. iOS 网络编程模式总结

    IOS 可以采用三类api 接口进行网络编程,根据抽象层次从低到高分别为socket方式.stream方式.url 方式. 一 .socket 方式 IOS 提供的socket 方式的网络编程接口为C ...

  6. C++:流类库与输入输出

    7.2.1 C++的输入输出流 ios:流基类(抽象类) istream:通用输入流类和其他输入流的基类 ostream:通用输出流类和其他输出类的基类 iostream:通用输入输出流类和其他输入输 ...

  7. c/c++ 浮点型处理

    #include <stdio.h> #include <iostream> #include <string> #include <string.h> ...

  8. 前端构建工具Gulp的学习和使用

    前几天刚鼓捣了Grunt的使用,结果文档还没捂热,老大说我们还是用gulp吧,搞得我又得来整gulp,眼泪流成河了,真是不晓得底层人民的辛苦啊.不过经过对gulp的学习,发现很好用,比grunt舒服! ...

  9. 第01章-Java SE8的流库

    从迭代到流的操作 流表面上看起来和集合很类似,都可以让我们转换和获取数据,但是它们之间存在着显著的差异 流并不存储其元素,这些元素可能存储在底层的集合中,或者是按需生成的 流的操作不会修改其数据源 流 ...

随机推荐

  1. Library:python-memcached on Windows

    1 install memcached 1.4.4 Windows 32-bit  2 cd into the base file and type memcached.exe -d install ...

  2. eclipse问题解决(link方式安装插件失败)

    使用 link 方式,离线安装 eclipse 插件时,经常失败. 一.常见的失败情况 link方式配好后,eclipse 启动,没有弹出任何信息. 查看当前工作空间——.metadata——.log ...

  3. BZOJ 2432 兔农

    Description 农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小朋友在讨论兔子繁殖的问题. 问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这对兔子从第三个月 ...

  4. check sql server edition (version 版本)

    SELECT @@VERSION refer : https://www.mssqltips.com/sqlservertip/1140/how-to-tell-what-sql-server-ver ...

  5. 【C++基础之十五】内联函数

    1.优点 为什么使用内联函数,而不使用宏定义,虽然宏本身采用的展开来替代函数调用的压栈出栈返回等操作,提高了代码的效率,但是会有两个问题: (1)边际效应 宏只是展开代码而已,所以在一些操作符的优先级 ...

  6. Unity NGUI 创建简单的按钮

    Unity版本:4.5.1 NGUI版本:3.6.5 注意NGUI版本,网上的大部分教程都是2.x版本的,在步骤上面略有不同,此文适合初学者. 示例: 通过NGUI创建一个背景和按钮. 1.首先创建一 ...

  7. OpenCV 2.4.3在VS2010上的应用

    一.下载和安装:    1.OpenCV 2.4.3下载:http://www.opencv.org.cn/index.php/Download#Version_2.4.3    2.下载完成后,解压 ...

  8. (转载)dl,dt,dd标记在网页中要充分利用

    (转载)http://www.jzxue.com/html/css/264I6DG6.html 我们在制作网页过程中用到列表时一般会使用<ul>或者<ol>标签,很少用刑< ...

  9. 字符串[未AC](后缀自动机):HEOI 2016 str

    超级恶心,先后用set维护right,再用主席树维护,全部超时,本地测是AC的.放心,BZOJ上还是1S限制,貌似只有常数优化到一定境界的人才能AC吧. 总之我是精神胜利了哦耶QAQ #include ...

  10. Tomcat死机报OutOfMemoryError: PermGen space错误

    最近,用户没怎么使用系统,页面就卡死,访问不了.仔细一看是Tomcat假死,好几次都这样.重启也慢的很,很着急.最后,看了下 conf/logs 里的配置文件,发现是 OutOfMemoryError ...