美文网首页
NSInputStream 和 NSOutputStream

NSInputStream 和 NSOutputStream

作者: 铁头娃_e245 | 来源:发表于2019-10-08 16:05 被阅读0次

    什么是Stream

    Stream翻译成为流,它是对我们读写文件的一个抽象。你可以这样想象,当你读文件和写文件的时候,文件的内容就像水流一样哗哗的 像你流过来或者流给别人,这样岂不是很爽。而Stream就帮我们做了这样的事情,实际上,它是把文件的内容,一小段一小段的读出或 写入,来到达这样的效果

    NSStream

    NSStream 是Cocoa平台下对流这个概念的实现类, NSInputStream 和 NSOutputStream 则是它的两个子类,分别对应了读文件和 写文件。

    从下图中可以看出,NSInputStream可以从文件、socket和NSData对象中获取数据;NSOutputStream可以将数据写入文件、socket、内存缓存和NSData对象中 (本文以读yuv和pcm文件为例)


    官方图片

    NSInputStream

    NSInputStream 对应的是读文件,所以要记住它是要将文件的内容读到内存(你声明的一段buffer)里

    //从流中读取数据到 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;
    

    从NSInputStream中读入数据包括几个步骤:

    1. 从数据源创建和初始化一个NSInputStream实例
    2. 设置好它的delegate(签代理)
    3. 将输入流对象配置到一个run loop,open the stream
    4. 通过流对象的delegate函数处理事件
    5. 当所有数据读完,进行流对象的内存处理

    下面一段是测试代码

    //第一步
    NSString* filePath = [[NSBundle mainBundle] pathForResource:@"XXX_640*640" ofType:@"yuv"];
    // inputSteam是NSInputStream实例变量
    self.inputYuvStream = [NSInputStream inputStreamWithFileAtPath:filePath];
    //第二步
    self.inputYuvStream.delegate = (id<NSStreamDelegate>)self;
    //第三步
    [self.inputYuvStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [self.inputYuvStream open];
    
    //代理方法: 一旦 open 流对象,流对象会一直向其delegate发送stream:handleEvent: 消息直到到达了流对象的末尾
    - (void)stream:(NSInputStream *)stream handleEvent:(NSStreamEvent)eventCode {
        
        switch(eventCode) {
            case NSStreamEventOpenCompleted: // 文件打开成功
                NSLog(@"文件打开,准备读取数据");
                break;
            case NSStreamEventHasBytesAvailable: // 读取到数据
            {
                //第四步
                if (self.inputYuvStream == stream) {
                    int dataSize = 640*640*3/2;   //这里公式为:视频高*视频宽*3/2
                    uint8_t buf[dataSize];    //声明一个大小为dataSize的uint8_t类型数组buf
                    NSInteger len = 0;
                    len = [stream read:buf maxLength:dataSize];// 读取数据(将dataSize长度存储到buf中)
                    if (len > 0) {   //说明数据没有读完
                        [NSThread sleepForTimeInterval:0.03];   // 30毫秒 (加延迟操作播放出流畅的画面)
                    }else {
                        //数据被读取完毕或者读取失败
                    }
    
                    //如果需要data存储每次读取的数据, 如下代码
                    if (!data) {
                        data = [NSMutableData data];
                    }
                    if (len) {
                        [data appendBytes:(const void *)buf length:len];
                    }
                    NSLog(@"数据长度:%lu",data.length);
                }
                
                if (self.inputPcmStream == stream) {
                    int dataSize = 640;
                    uint8_t buf[dataSize];
                    NSInteger len = 0;
                    len = [stream read:buf maxLength:dataSize];// 读取数据
                    if (len > 0) {
                        [NSThread sleepForTimeInterval:0.02];   // 20毫秒
                    }else {
                        //数据被读取完毕或者读取失败
                    }
                }
                break;
            }
            case NSStreamEventEndEncountered: // 文件读取结束
            {
                    NSLog(@"pcm数据读取结束");
                    //第五步
                    [stream close];
                    [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
                                      forMode:NSDefaultRunLoopMode];
                    stream = nil;
                    
                    //如果需要循环读取该文件在重新初始化并open
                    NSString* filePath = [[NSBundle mainBundle] pathForResource:@"test_data_160" ofType:@"pcm"];
                    // inputSteam是NSInputStream实例变量
                    self.inputPcmStream = [NSInputStream inputStreamWithFileAtPath:filePath];
                    self.inputPcmStream.delegate = (id<NSStreamDelegate>)self;
                    [self.inputPcmStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
                    [self.inputPcmStream open];
                
                break;
            }
            default:
                break;
        }
        
    }
    

    NSOutputStream

    NSOutputStream 对应的是写文件,它是要将已存在的内存(buffer)里的数据写入文件

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

    从NSOutputStream中读入数据包括几个步骤:

    1. 使用存储写入数据的存储库创建和初始化一个NSOutputSteam实例,并且设置它的delegate。
    2. 设置好它的delegate(签代理)
    3. 将这个流对象布置在一个runloop上并且open the stream。
    4. 处理流对象向其delegate发送的事件消息。
    5. 如果流对象向内存中写入了数据,那么可以通过使用NSStreamDataWrittenToMemoryStreamKey属性获取数据。
    6. 当没有数据可供写入时,清理流对象。

    下面一段是测试代码

    //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];
    
    - (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);
    }
    

    用途

    NSInputStream 和 NSOutputStream 常用与网络传输中,比如要将一个很大的文件传送给服务器,那么NSInputStream这时候是 很好的选择, 我们可以查看到 NSURLRequest 有一个属性叫HTTPBodyStream, 这时只要设置好一个NSInputStream的实例就可以 了,最大的好处就是可以节省我们很多的内存。

    另外要说明的是,NSInputStream 和 NSOutputStream其实是对 CoreFoundation 层对应的CFReadStreamRef 和 CFWriteStreamRef 的高层抽象。在使用CFNetwork时,常常会使用到CFReadStreamRef 与 CFWriteStreamRef。

    参考文献

    NSInputStream和NSOutputStream的分析
    NSInputStream 和 NSOutputStream
    NSOutputStream向输出流写数据
    iOS 播放pcm文件

    相关文章

      网友评论

          本文标题:NSInputStream 和 NSOutputStream

          本文链接:https://www.haomeiwen.com/subject/mtkbyctx.html