什么是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中读入数据包括几个步骤:
- 从数据源创建和初始化一个NSInputStream实例
- 设置好它的delegate(签代理)
- 将输入流对象配置到一个run loop,open the stream
- 通过流对象的delegate函数处理事件
- 当所有数据读完,进行流对象的内存处理
下面一段是测试代码
//第一步
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中读入数据包括几个步骤:
- 使用存储写入数据的存储库创建和初始化一个NSOutputSteam实例,并且设置它的delegate。
- 设置好它的delegate(签代理)
- 将这个流对象布置在一个runloop上并且open the stream。
- 处理流对象向其delegate发送的事件消息。
- 如果流对象向内存中写入了数据,那么可以通过使用
NSStreamDataWrittenToMemoryStreamKey
属性获取数据。- 当没有数据可供写入时,清理流对象。
下面一段是测试代码
//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文件
网友评论