博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[深入浅出Cocoa]iOS网络编程之NSStream
阅读量:6914 次
发布时间:2019-06-27

本文共 6144 字,大约阅读时间需要 20 分钟。

[深入浅出Cocoa]iOS网络编程之NSStream

罗朝辉 ()

本文遵循“”创作公用协议
 

一,NSStream简介

首先来回顾下。在前文《》中,提到iOS网络编程层次模型分为三层:
  • Cocoa层:NSURL,Bonjour,Game Kit,WebKit
  • Core Foundation层:基于 C 的 CFNetwork 和 CFNetServices
  • OS层:基于 C 的 BSD socket
前文《》 和《》 讲了最底层的 socket 和Core Foundation层的 CFNetwork,本文将介绍位于 Cocoa 中的 NSStream。NSStream 其实只是用 Objective-C 对 CFNetwork 的简单封装, 它使用名为 NSStreamDelegate 的协议来实现 CFNetwork 中的回调函数的作用,同样,runloop 也与 NSStream 结合的很好。NSStream 有两个实体类:NSInputStream 和 NSOutputStream,分别对应 CFNetwork 中的 CFReadStream 和 CFWriteStream。
 
本文示例代码请查看:

二,NSStream 类接口简介

NSStream 类有如下接口:

- (void)open;

- (void)close;

- (id <NSStreamDelegate>)delegate;

- (void)setDelegate:(id <NSStreamDelegate>)delegate;

- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;

- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;

- (NSStreamStatus)streamStatus;

- (NSError *)streamError;

NSStream 的一些接口与 CFNetwork 类似,如打开,关闭,获取状态和错误信息,以及和 runloop 结合等在这里就不再重复了。前面提到 NSStream 是通过 NSStreamDelegate 来实现 CFNetwork 中的回调函数,这个可选的协议只有一个接口:

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;

NSStreamEvent 是一个流事件枚举:

typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {

    NSStreamEventNone = 0,

    NSStreamEventOpenCompleted = 1UL << 0,

    NSStreamEventHasBytesAvailable = 1UL << 1,

    NSStreamEventHasSpaceAvailable = 1UL << 2,

    NSStreamEventErrorOccurred = 1UL << 3,

    NSStreamEventEndEncountered = 1UL << 4

};

这些事件枚举的含义也和 CFNetwork 中的 CFStreamEventType 类似,在此也就不再重复了。

NSInputStream 类有如下接口:

- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;

从流中读取数据到 buffer 中,buffer 的长度不应少于 len,该接口返回实际读取的数据长度(该长度最大为 len)。

- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len;

获取当前流中的数据以及大小,注意 buffer 只在下一个流操作之前有效。

- (BOOL)hasBytesAvailable;

检查流中是否还有数据。

NSOutputStream 类有如下接口:

- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len;

将 buffer 中的数据写入流中,返回实际写入的字节数。

- (BOOL)hasSpaceAvailable;

检查流中是否还有可供写入的空间。

从这些接口可以看出,NSStream 真的就是 CFNetwork 上的一层简单的 Objective-C 封装。但 iOS 中的 NSStream 不支持 NShost,这是一个缺陷,苹果也意识到这问题了(),我们可以通过 NSStream 的扩展函数来实现该功能:

@implementation NSStream(StreamsToHost)+ (void)getStreamsToHostNamed:(NSString *)hostName                         port:(NSInteger)port                  inputStream:(out NSInputStream **)inputStreamPtr                 outputStream:(out NSOutputStream **)outputStreamPtr{    CFReadStreamRef     readStream;    CFWriteStreamRef    writeStream;        assert(hostName != nil);    assert( (port > 0) && (port < 65536) );    assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) );        readStream = NULL;    writeStream = NULL;        CFStreamCreatePairWithSocketToHost(                                       NULL,                                       (__bridge CFStringRef) hostName,                                       port,                                       ((inputStreamPtr  != NULL) ? &readStream : NULL),                                       ((outputStreamPtr != NULL) ? &writeStream : NULL)                                       );        if (inputStreamPtr != NULL) {        *inputStreamPtr  = CFBridgingRelease(readStream);    }    if (outputStreamPtr != NULL) {        *outputStreamPtr = CFBridgingRelease(writeStream);    }}@end

 

三,客户端示例代码

与前面的示例类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作:

NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]];    NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self                                                          selector:@selector(loadDataFromServerWithURL:)                                                            object:url];    [backgroundThread start];

然后在 loadDataFromServerWithURL 中创建 NSInputStream,并设置其 delegate,将其加入到 run-loop 的事件源中,然后打开流,运行 runloop:

- (void)loadDataFromServerWithURL:(NSURL *)url{    NSInputStream * readStream;    [NSStream getStreamsToHostNamed:[url host]                               port:[[url port] integerValue]                        inputStream:&readStream                       outputStream:NULL];        [readStream setDelegate:self];    [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];    [readStream open];        [[NSRunLoop currentRunLoop] run];}

因为我们将 KSNSStreamViewController 当作 NSInputStream 的 delegate,因此要在 KSNSStreamViewController 中实现该 delgate:

#pragma mark NSStreamDelegate- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode{    NSLog(@" >> NSStreamDelegate in Thread %@", [NSThread currentThread]);        switch (eventCode) {        case NSStreamEventHasBytesAvailable: {            if (_receivedData == nil) {                _receivedData = [[NSMutableData alloc] init];            }                        uint8_t buf[kBufferSize];            int numBytesRead = [(NSInputStream *)stream read:buf maxLength:kBufferSize];                        if (numBytesRead > 0) {                [self didReceiveData:[NSData dataWithBytes:buf length:numBytesRead]];                            } else if (numBytesRead == 0) {                NSLog(@" >> End of stream reached");                            } else {                NSLog(@" >> Read error occurred");            }                        break;        }                    case NSStreamEventErrorOccurred: {            NSError * error = [stream streamError];            NSString * errorInfo = [NSString stringWithFormat:@"Failed while reading stream; error '%@' (code %d)", error.localizedDescription, error.code];                        [self cleanUpStream:stream];                        [self networkFailedWithErrorMessage:errorInfo];        }                    case NSStreamEventEndEncountered: {                        [self cleanUpStream:stream];                        [self didFinishReceivingData];            break;        }                    default:            break;    }}

当数据读取完毕或者读取失败时,调用 cleanUpStream 方法来关闭流:

- (void)cleanUpStream:(NSStream *)stream{    [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];    [stream close];        stream = nil;}

 

四,结语

通过上面的示例演示,我们可以看到 NSStream 只是用 Objective-C 对 CFNetwork 的一层简单封装,但确实大大方便了我们使用 socket 进行编程,因此在大多数情况下,我们都应该优先使用 NSStream 进行 socket 编程。

 

转载地址:http://afacl.baihongyu.com/

你可能感兴趣的文章
quota(linux下的磁盤配額)
查看>>
【MySQL数据库开发之四】MySQL 处理模式/常用查询/模式匹配等
查看>>
Linux NFS服务器配置
查看>>
C++实现计数排序
查看>>
PPT组件Aspose.Slides V17.8发布 | 支持PP2010 PPTX与嵌入式视频
查看>>
Postfix全功能 (1)
查看>>
DOS系统功能调用表(INT 21H)
查看>>
作为JavaScript开发人员,这些必备的VS Code插件你都用过吗
查看>>
未来云世界畅想
查看>>
进程与计划任务
查看>>
组合模式
查看>>
linux文件操作之ls、mkdir、rmdir、cp命令使用
查看>>
观察最大堆的插入/删除元素后最大堆的变化
查看>>
oracle导入时IMP-00010: 不是有效的导出文件, 头部验证失败
查看>>
R:corrplot包
查看>>
用SPK技术分析,泰坦尼克号沉船之后哪些人活下来了
查看>>
多线程(八)---单例模式并发访问
查看>>
堵车预测神器诞生 浙江已用来治疗高速顽疾
查看>>
Android Span的简单使用
查看>>
centos7 挂载数据盘
查看>>