流NSStream

在通信路径中串行传输的连续的比特位序列(二进制数据串)。即,二进制数据串在端与端之间的传输。
从编码的角度来看,流是单向的,因此流可以分输入流输出流
在 Cocoa 中提供了 NSStreamNSInputStreamNSOutputStream三个类,来实现数据通过流的方式在文件、内存、网络之间的传输。

NSStream 不支持套接字连接,要实现远程客户端套接字交互,可以使CFStream 的相关接口。
NSStream及其子类进行的是比较底层的开发,对于某些特殊的需求如果有顶层的Cocoa API更加适合的话(比如NSURLNSFileHandle),那么就用顶层的API进行编程

NSStream

NSStream是一个抽象类,定义了一些基本的方法和属性。它是NSInputStreamNSOutputStream类的父类。

  • 使用
    常用方法如下:
- (void)open;
- (void)close;
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;

用完就关
当流创建并开启后,会占用一些系统资源,所以流使用结束后,应主动关闭。关闭后的流不能再次开启,但其相关属性仍可以访问。如果流被添加到运行循环RunLoop中,关闭时应将其移出RunLoop

NSStream是建立在Core FoundationCFStream层之上的。这层紧密的关系意味着NSStream的具体子类NSInputStreamNSOutputStreamCore Foundation中的CFReadStreamCFWriteStream是一一对应的。
尽管CocoaCore Foundation的stream APIs有很大的相似性,但是它们的实现却不尽相同:
NSStream类使用delegate模式来实现异步操作(比如将其布置在run loop之上),而Core Foundation使用客户端的回调。
Core Foundation的stream类型设置的是client(在Core Foundation中叫做context),NSStream中设置的delegate,这是两个不同的概念,不应该把设置delegate和设置context搞混淆。

  • 流的状态NSStreamStatus
    @property (readonly) NSStreamStatus streamStatus;
typedef NS_ENUM(NSUInteger, NSStreamStatus) {
    NSStreamStatusNotOpen = 0,
    NSStreamStatusOpening 
    NSStreamStatusOpen 
    NSStreamStatusReading 
    NSStreamStatusWriting 
    NSStreamStatusAtEnd 
    NSStreamStatusClosed 
    NSStreamStatusError 
};
  • 流的属性
    大部分属性是用于处理网络安全和配置。
    以下方法可以设置/获取流相关的属性配置
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
- (nullable id)propertyForKey:(NSStreamPropertyKey)key;
- (BOOL)setProperty:(nullable id)property forKey:(NSStreamPropertyKey)key;
#else
- (nullable id)propertyForKey:(NSString *)key;
- (BOOL)setProperty:(nullable id)property forKey:(NSString *)key;
#endif

属性的键NSStreamPropertyKey:
NSStreamSocketSecurityLevelKey套接字安全选项
NSStreamSOCKSProxyConfigurationKey套接字代理的配置信息
NSStreamDataWrittenToMemoryStreamKey输出流写入内存中的数据*
NSStreamFileCurrentOffsetKey基于文件的流的数据偏移量*
NSStreamNetworkServiceType 指定流的用途

  • 流的代理NSStreamDelegate
    @property (nullable, assign) id delegate;
    代理可以不指定。如果不指定,那么流本身即为代理对象,应实现代理方法:
    NSStreamDelegate 协议中的唯一的方法:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
    switch (eventCode)
    {
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
            
        default:
            break;
    }
}

用来处理与流相关的事件。

流一旦打开后,将会持续发送stream:handleEvent:消息给代理对象,直到流结束为止。这个消息接收一个NSStreamEvent常量作为参数,以标识事件的类型。

  • 流的事件NSStreamEvent
    可能的事件如下:
NSStreamEventNone 无事件发生
NSStreamEventOpenCompleted 成功打开流
NSStreamEventHasBytesAvailable 流中有数据可读。代理向该stream发送`read:maxLength:`消息,从流中读取数据
NSStreamEventHasSpaceAvailable 可以向流中写入数据。代理向该stream发送`write:maxlength:`消息,向流中写入数据。
NSStreamEventErrorOccurred 发生错误
NSStreamEventEndEncountered 达到流末尾

NSInputStream

NSInputStream是输入流,流中的数据来自本地文件或网络资源。应用从输入流中读取到数据后,便可进行必要的处理。

  • 创建
    创建 NSInputStream 实例对象时,应当指定数据源,可以是文件、内存、NSData对象、或网络资源。

  • 读取数据
    属性hasBytesAvailable表示当前输入流中,是否有可以读取的数据。如果为 YES 那么可以调用流的read方法进行读取:

// buffer 是提供的缓存地址
// len 是可以读取的数据的最大字节数
//返回:实际读取的数据字节数
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;
switch (eventCode) {
        case NSStreamEventHasBytesAvailable:{
            if (!data) 
                data = [NSMutableData data];
             
            uint8_t buf[1024];
            unsigned int len = 0;
            len = [(NSInputStream *)aStream read:buf maxLength:1024];  // 读取数据
            if (len) 
                [data appendBytes:(const void *)buf length:len];
        }
        break;
}

关闭,清理流对象。
NSInputStream读取到流的结尾时,会发送一个NSStreamEventEndEncountered事件给代理,代理对象应该销毁流对象,此过程应该与创建时相对应。

{
    switch (eventCode) {
        case NSStreamEventEndEncountered:
        {
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            aStream = nil;
        }
            break;
    }
}

NSOutputStream

NSOutputStream 是输出流,将处理后的数据写入输出流中,而后数据传输到目标。这个目标可以是本地文件,或网上的某个地址。

  • 创建
    创建NSOutputStream实例对象时,应当指定数据输出的目标,可以是内存、本地文件、NSData对象、或网络地址。

如果输出流的目标为内存,则可使用 NSStreamDataWrittenToMemoryStreamKey 获取最终输出的数据。

  • 写入数据
    属性hasSpaceAvailable值如果为YES ,则表示可以向流中write写入数据:
// buffer 从指定的缓存中向流中写入数据
// len 可以写入的数据最大字节数
//返回:实际向流中写入的数据字节数
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len;
{
    switch (eventCode) {
        case NSStreamEventHasSpaceAvailable:
        {
            uint8_t *readBytes = (uint8_t *)[data mutableBytes];
            readBytes += byteIndex;
            int data_len = [data length];
            unsigned int len = (data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex);
            uint8_t buf[len];
            
            (void)memcpy(buf, readBytes, len);
           
            len = [aStream write:(const uint_8 *)buf maxLength:len];
            byteIndex += len;
            break;
        }
    }
}
  • 流的轮循处理(阻塞主线程)
    在流的处理过程中,除了将流放入run loop来处理流事件外,还可以对流进行轮循处理。
    将流处理数据的过程放到一个while循环中,并在循环中不断地去询问流是否有可用的数据供读取(hasBytesAvailable==YES),或可用的空间供写入(hasSpaceAvailable==YES)。当处理到流的结尾时,我们跳出循环结束流的操作。
 - (void)createNewFile
{
  NSOutputStream *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;
}

这种处理方法的问题在于它会阻塞当前线程,直到流处理结束为止。

  • 错误处理
    当流出现错误时,会停止对流数据的处理。一个流对象在出现错误时,不能再用于读或写操作。

告知错误的发生几种方式:

1、如果流被放到run loop中,对象会发送一个NSStreamEventErrorOccurred事件到代理对象的代理方法stream:handleEvent:
任何时候,可以调用streamStatus属性来查看是否发生错误(返回NSStreamStatusError)

2、如果在通过调用write:maxLength:写入数据到NSOutputStream对象时返回-1,则发生一个写错误。

一旦确定产生错误时,我们可以调用流对象的streamError属性来查看错误的详细信息。

开发步骤

1、网络连接设置
设置网络连接,绑定到主机和端口
设置输入流和输出流的代理,监听数据流的状态
将输入输出流添加至运行循环
打开输入流和输出流

@interface ViewController (){
    NSInputStream *_inputStream;
    NSOutputStream *_outputSteam;
}

。。。

// 初始化连接
- (IBAction)connectToServer:(id)sender {
    
    NSString *host = @"127.0.0.1";    // 服务器主机号
    int port = 12345;                  // 端口号
    
    // 定义输入输出流
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;
    
    // 分配输入输出流的 内存空间
    CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
    
    // 把C语言的输入输出流 转成OC对象
    _inputStream = (__bridge NSInputStream *)readStream;
    _outputSteam = (__bridge NSOutputStream *)(writeStream);
    
    // 设置代理,监听数据接收的状态
    _outputSteam.delegate = self;
    _inputStream.delegate = self;
    
    // 把输入输入流添加到主运行循环(RunLoop)
    // 主运行循环是监听网络状态
    [_outputSteam scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    [_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
   
    // 打开输入输出流
    [_inputStream open];
    [_outputSteam open];
}

2、发送消息给服务器
3、有可读取字节时,读取服务器返回的内容
4、到达流末尾时,关闭流,同时并从主运行循环中删除

//实现代理方法 (回调是在主线程)
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            NSLog(@"成功连接建立,形成输入输出流的传输通道");
            break;
            
        case NSStreamEventHasBytesAvailable:
            NSLog(@"有数据可读");
            [self readData];
            break;
            
        case NSStreamEventHasSpaceAvailable:
            NSLog(@"可以发送数据");
            [self writeData];
            break;
            
         case NSStreamEventErrorOccurred:
            NSLog(@"有错误发生,连接失败");
            break;
            
         case NSStreamEventEndEncountered:
            NSLog(@"正常的断开连接");
            //把输入输入流关闭,而还要从主运行循环移除
            [_inputStream close];
            [_outputSteam close];
            [_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            [_outputSteam removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
            break;
        default:
            break;
    }
}

# 读取服务器 返回的数据
-(void)readData{
    //定义缓冲区 ,只能存储1024字节
    uint8_t buf[1024];
    
    // 读取数据,len为从服务器读取到的实际字节数
    NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];
    
    // 把缓冲区里的实现字节数转成字符串
    NSString *receiverStr = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding];
    NSLog(@"%@",receiverStr);

}

-(void)writeData{

    int indexLen = 1024;

    uint8_t buffer[1024];

    int written = (int)[_outputSteam write: buffer maxLength: indexLen];

}

示例

@interface ViewController ()
 
@property (nonatomic,strong) NSString *filePath;
 
@property (nonatomic,assign) int location;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self createTestFile];
}
 
// 创建一个测试文件。
 
- (void)createTestFile{
    _filePath = NSHomeDirectory();
    _filePath = [_filePath stringByAppendingPathComponent:@"Documents/test_data.txt"];
    NSError *error;
    NSString *msg = @"测试数据,需要的测试数据,测试数据显示。";
    bool  isSuccess = [msg writeToFile:_filePath atomically:true encoding:NSUTF8StringEncoding error:&error];
    if (isSuccess) {
        NSLog(@"数据写入成功了");
    }else{
        NSLog(@"error is %@",error.description);
    }
    
    // 追加数据
    NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:_filePath];
    [handle seekToEndOfFile];
    NSString *newMsg = @".....我将添加到末尾你处";
    NSData *data = [newMsg dataUsingEncoding:NSUTF8StringEncoding];
    [handle writeData:data];
    [handle closeFile];
}
 
 
 
// NSOutPutStream 处理  写
 
- (IBAction)outPutStramAction:(id)sender {
    
    NSString *path = @"/Users/yubo/Desktop/stream_ios.txt";
    NSOutputStream *writeStream = [[NSOutputStream alloc]initToFileAtPath:path append:true];
    
    // 手动创建文件, 如果是系统创建的话, 格式编码不一样。
    bool flag = [@"Ios----->" writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:nil];
    if (flag) {
        NSLog(@"创建成功");
    }
    
    writeStream.delegate = self;
    [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [writeStream open];
}
 
 
// NSInPutStream 处理   读
 
- (IBAction)inPutStreamAction:(id)sender {
    
    NSInputStream *readStream = [[NSInputStream alloc]initWithFileAtPath:_filePath];
    [readStream setDelegate:self];
    
    // 这个runLoop就相当于死循环,一直会对这个流进行操作。
    [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [readStream open];
}
 
 
#pragma mark  NSStreamDelegate代理 
 
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
    switch (eventCode) {
    
        case NSStreamEventHasSpaceAvailable:{ // 写
            
            NSString *content = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil];
            NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
            
            NSOutputStream *writeStream = (NSOutputStream *)aStream;
            [writeStream write:data.bytes maxLength:data.length];
            [aStream close];
            
            // 用buf的还没成功
            
//          [writeStream write:<#(nonnull const uint8_t *)#> maxLength:<#(NSUInteger)#>]; 乱码形式
            
            break;
        }
        case NSStreamEventHasBytesAvailable:{ // 读
                uint8_t buf[1024];
                NSInputStream *reads = (NSInputStream *)aStream;
                NSInteger blength = [reads read:buf maxLength:sizeof(buf)];
                if (blength != 0) {
                    NSData *data = [NSData dataWithBytes:(void *)buf length:blength];
                    NSString *msg = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
                    NSLog(@"文件内容如下:----->%@",msg);
                }else{
                    [aStream close];
                }
                 break;
             }
        case NSStreamEventErrorOccurred:{// 错误处理
        
                NSLog(@"错误处理");
                break;
        
            }
        case NSStreamEventEndEncountered: {
               [aStream close];
               break;
           }
        case NSStreamEventNone:{// 无事件处理
        
                NSLog(@"无事件处理");
                break;
            }
        case  NSStreamEventOpenCompleted:{// 打开完成
            
                NSLog(@"打开文件");
                break;
            }
        default:
            break;
     }
}
 
@end

其他资料

https://www.cnblogs.com/wangxiaorui/p/5029372.html

你可能感兴趣的:(流NSStream)