NSInputStream和NSOutputStream的分析

NSStream

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

在Cocoa中包含三个与流相关的类:NSStream、NSInputStream和NSOutputStream。NSStream是一个抽象基类,定义了所有流对象的基础接口和属性。NSInputStream和NSOutputStream继承自NSStream,实现了输入流和输出流的默认行为。从下图中可以看出,NSInputStream可以从文件、socket和NSData对象中获取数据;NSOutputStream可以将数据写入文件、socket、内存缓存和NSData对象中

官方图片.png

NSInputStream

//从流中读取数据到 buffer 中,buffer 的长度不应少于 len,
//该接口返回实际读取的数据长度(该长度最大为 len)
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;
//获取当前流中的数据以及大小,注意 buffer 只在下一个流操作之前有效。
- (BOOL)getBuffer:(uint8_t * _Nullable * _Nonnull)buffer length:(NSUInteger *)len;
//检查流中是否还有数据。
@property (readonly) BOOL hasBytesAvailable;

在Cocoa中,从NSInputStream实例读取包含几个步骤:

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

创建和初始化NSInputStream对象

// inputSteam是NSInputStream实例变量
NSString *path = [[NSBundle mainBundle] pathForResource:@"abc" ofType:@"pcm"];
inputSteam = [[NSInputStream alloc] initWithFileAtPath:path];
inputSteam.delegate = self;
[inputSteam scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputSteam open];

处理代理事件

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventNone:{
            NSLog(@"NSStreamEventNone");
        }
            break;
        case NSStreamEventOpenCompleted:{
            NSLog(@"NSStreamEventOpenCompleted");
        }
            break;
        case NSStreamEventHasBytesAvailable:
        {
            if (!data) {
                data = [NSMutableData data];
            }
            uint8_t buf[1024];
            NSInteger len = 0;
            len = [(NSInputStream *)aStream read:buf maxLength:1024];// 读取数据
            if (len) {
                [data appendBytes:(const void *)buf length:len];
            }
            NSLog(@"数据长度:%lu",data.length);
        }
            break;
        case NSStreamEventHasSpaceAvailable:{
            NSLog(@"NSStreamEventHasSpaceAvailable");
        }
            break;
        //此处处理错误事件
        case NSStreamEventErrorOccurred:
        {
            NSError * error = [aStream streamError];
            NSString * errorInfo = [NSString stringWithFormat:@"Failed while reading stream; error '%@' (code %ld)", error.localizedDescription, error.code];
            NSLog(@"%@",errorInfo);
            break;
        }
            
        case NSStreamEventEndEncountered:
        {
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop]
                              forMode:NSDefaultRunLoopMode];
            aStream = nil;
        }
            break;
        default:
            break;
    }
    NSLog(@"查看inputSteam状态值:%lu",inputSteam.streamStatus);
}

注意:一旦打开了流对象,它就会不断stream:handleEvent:向其委托发送消息(只要代理继续在流上放置字节(操作了读写操作)),直到它遇到流的末尾,这个消息接收一个NSStreamEvent常量作为参数,以标识事件的类型。对于NSInputStream对象,主要的事件类型包括NSStreamEventOpenCompletedNSStreamEventHasBytesAvailableNSStreamEventEndEncountered


NSOutputStream

//将 buffer 中的数据写入流中,返回实际写入的字节数。
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len;
//检查流中是否还有可供写入的空间。
@property (readonly) BOOL hasSpaceAvailable;

在Cocoa中 使用NSOutputStream实例写入输出流需要几个步骤:

  • 使用要写入的数据创建和初始化一个NSOutputStream实例,设置代理对象
  • 在运行循环上计划流对象并打开流。
  • 处理流对象报告给其委托的事件。
  • 如果流对象已将数据写入内存,请通过请求NSStreamDataWrittenToMemoryStreamKey属性获取数据。
  • 当没有更多数据要写入时,处理流对象。

创建和初始化NSOutputStream对象

//oStream是一个实例变量
//(举个例子,输出到文件)
//NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]stringByAppendingPathComponent:@"111.pcm"];
//oStream = [[NSOutputStream alloc] initToFileAtPath:path append:YES];
oStream = [[NSOutputStream alloc] initToMemory];
oStream.delegate = self;
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSDefaultRunLoopMode];
[oStream open];

处理代理事件

case NSStreamEventHasSpaceAvailable:
        {
            if (!data) {
                NSString *path = [[NSBundle mainBundle] pathForResource:@"abc" ofType:@"pcm"];
                data = [NSMutableData dataWithContentsOfFile:path];
            }
            uint8_t *readBytes = (uint8_t *)[data mutableBytes];
            readBytes += byteIndex; // instance variable to move pointer
            NSInteger data_len = [data length];
            NSInteger len = ((data_len - byteIndex >= 1024) ?
                                1024 : (data_len-byteIndex));
            uint8_t buf[len];
            (void)memcpy(buf, readBytes, len);
            len = [oStream write:(const uint8_t *)buf maxLength:len];
            byteIndex += len;
            break;
        }
//关闭并释放NSOutputStream对象
case NSStreamEventEndEncountered:
        {
            NSData *newData = [oStream propertyForKey:
                               NSStreamDataWrittenToMemoryStreamKey];
            if (!newData) {
                NSLog(@"No data written to memory!");
            } else {
                NSLog(@"数据总长度%ld",newData.length);
            }
            [oStream close];
            [oStream removeFromRunLoop:[NSRunLoop currentRunLoop]
                              forMode:NSDefaultRunLoopMode];
            oStream = nil; // oStream is instance variable
        }
            break;

注意:输出流主要的事件类型包括NSStreamEventOpenCompletedNSStreamEventHasSpaceAvailableNSStreamEventEndEncountered


Polling Versus Run-Loop Scheduling

流处理的潜在问题是阻塞。正在写入或读取流的线程可能无限期地等待,直到流上有空间将字节或字节放在可以读取的流上。实际上,线程受流的支配,这可能会给应用程序带来麻烦。阻塞特别是socket流的问题,因为它们依赖于来自远程主机的响应。

在Cocoa中,有两种方法来处理流事件:

  • Run-loop:将流对象安排在一个运行循环中,以便只有在不太可能发生阻塞时,委托才接收到报告与流相关的事件的消息。对于读写操作,相关的NSStreamEvent常量是NSStreamHasBytesAvailableNSStreamHasSpaceAvailable

  • Polling。仅在流的末尾或错误时被破坏的闭环中,一直在询问流对象是否有(对于读流)可读的字节或(对于写流)可写的空间。相关的方法有hasBytesAvailable (NSInputStream)和hasSpaceAvailable (NSOutputStream)。

注意:Run-loop几乎总是优于Polling,这就是为什么“ 从输入流读取”和“ 写入输出流”中的代码示例中仅显示Run-loop的使用。通过Polling,程序将被锁定在一个紧凑的循环中,等待可能会或可能不会即将发生的流事件。通过Run-loop,程序可以启动并执行其他操作,因为它知道在有流事件需要处理时会通知它。此外,Run-loop使您不必管理状态,并且比Polling更有效。Polling也是CPU密集型的; 你可以用你的处理时间做其他事情。

下面这个是官方的代码事例,其实就是while循环一直在操作数据,然后在内部判断何时中断。这种处理方法的问题在于它会阻塞当前线程,直到流处理结束为止,才继续进行后面的操作。而这种问题在处理网络socket流时尤为严重,我们必须等待服务端数据回来后才能继续操作。因此,通常情况下,建议使用run loop方式来处理流事件。

- (void)createNewFile {
    oStream = [[NSOutputStream alloc] initToMemory];
    [oStream open];
    uint8_t *readBytes = (uint8_t *)[data mutableBytes];
    uint8_t buf[1024];
    int len = 1024;
 
    while (1) {
        if (len == 0) break;
        if ( [oStream hasSpaceAvailable] ) {
        (void)strncpy(buf, readBytes, len);
        readBytes += len;
        if ([oStream write:(const uint8_t *)buf maxLength:len] == -1) {
            [self handleError:[oStream streamError]];
            break;
        }
        [bytesWritten setIntValue:[bytesWritten intValue]+len];
        len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 :
            [data length] - [bytesWritten intValue]);
        }
    }
    NSData *newData = [oStream propertyForKey:
        NSStreamDataWrittenToMemoryStreamKey];
    if (!newData) {
        NSLog(@"No data written to memory!");
    } else {
        [self processData:newData];
    }
    [oStream close];
    [oStream release];
    oStream = nil;
}

设置Socket流

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

- (IBAction)searchForSite:(id)sender
{
    NSString *urlStr = [sender stringValue];
    if (![urlStr isEqualToString:@""]) {
        NSURL *website = [NSURL URLWithString:urlStr];
        if (!website) {
            NSLog(@"%@ is not a valid URL");
            return;
        }
 
        CFReadStreamRef readStream;
        CFWriteStreamRef writeStream;
        CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 80, &readStream, &writeStream);
 
        NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
        NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
        [inputStream setDelegate:self];
        [outputStream setDelegate:self];
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [inputStream open];
        [outputStream open];
 
        /* Store a reference to the input and output streams so that
           they don't go away.... */
        ...
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的。流是在通信路径中串行传输的连续的比特位序...
    小鱼儿喜欢花无缺阅读 1,861评论 1 2
  • 流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的。流是在通信路径中串行传输的连续的比特位序...
    每天刷两次牙阅读 2,675评论 2 0
  • 流提供了一种简单的方式在不同和介质中交换数据,这种交换方式是与设备无关的。流是在通信路径中串行传输的连续的比特位序...
    磁针石阅读 14,844评论 8 45
  • 文 | 典典的蟹妈 全目录 |《里昂的奇幻之旅》 里昂在看不到尽头的黑暗中坠落……失重让他头晕,目眩,恶心,他想吐...
    典典的蟹妈阅读 629评论 11 9
  • “影帝局长”正在会上高谈阔论(图片来自网络) “你们放心,老百姓需要啥,我们就为老百姓服务啥。”“我们交通人,特别...
    辉声辉色阅读 324评论 0 0