美文网首页网络
NSInputStream和NSOutputStream的分析

NSInputStream和NSOutputStream的分析

作者: iOS扫地僧 | 来源:发表于2018-07-16 17:33 被阅读0次

    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.... */
            ...
        }
    }
    

    相关文章

      网友评论

        本文标题:NSInputStream和NSOutputStream的分析

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