美文网首页
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