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

转载 2013年12月04日 22:59:29

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

罗朝辉 (http://blog.csdn.net/kesalin/)

本文遵循“署名-非商业用途-保持一致”创作公用协议

 

一,NSStream简介

首先来回顾下。在前文《[深入浅出Cocoa]iOS网络编程之Socket》中,提到iOS网络编程层次模型分为三层:

  • Cocoa层:NSURL,Bonjour,Game Kit,WebKit
  • Core Foundation层:基于 C 的 CFNetwork 和 CFNetServices
  • OS层:基于 C 的 BSD socket

前文《iOS网络编程之Socket》 和《iOS网络编程之CFNetwork》 讲了最底层的 socket 和Core Foundation层的 CFNetwork,本文将介绍位于 Cocoa 中的 NSStream。NSStream 其实只是用 Objective-C 对 CFNetwork 的简单封装,它使用名为 NSStreamDelegate 的协议来实现 CFNetwork 中的回调函数的作用,同样,runloop 也与 NSStream 结合的很好。NSStream 有两个实体类:NSInputStream 和 NSOutputStream,分别对应 CFNetwork 中的 CFReadStream 和 CFWriteStream。

 

本文示例代码请查看:

https://github.com/kesalin/iOSSnippet/tree/master/KSNetworkDemo


二,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,这是一个缺陷,苹果也意识到这问题了(http://developer.apple.com/library/ios/#qa/qa1652/_index.html),我们可以通过 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 编程。

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

[深入浅出Cocoa]iOS网络编程之Socket [深入浅出Cocoa]iOS网络编程之Socket 罗朝辉 (http://www.cnblogs.com/kesalin/) 本...
  • yiyunhzy
  • yiyunhzy
  • 2013年08月26日 14:02
  • 697

IOS_使用CorePlot实现曲线图(可交互)

本文是来自本群【小七_IOS_太原】的力作,感谢小七的分享 看上这位技术达人可以联系Email:jiangzhoubai@gmail.com 最明显的地方附上Demo下载地址 ...
  • u011220580
  • u011220580
  • 2013年08月05日 16:22
  • 2929

【iOS】深入浅出 Cocoa 之消息

深入浅出 Cocoa 之消息    罗朝辉(http://blog.csdn.net/kesalin) 在入门级别的ObjC 教程中,我们常对从C++或Java 或其他面向对象语言转过来的...
  • chenglibin1988
  • chenglibin1988
  • 2014年08月25日 15:23
  • 653

[ios2] 深入浅出Cocoa之Bonjour网络编程【转】

http://www.cnblogs.com/kesalin/archive/2011/09/15/cocoa_bonjour.html 本文高度参考自 Tutorial: Networking a...
  • jinjiantong
  • jinjiantong
  • 2014年01月24日 08:35
  • 331

[Cocoa]深入浅出Cocoa之消息

在入门级别的ObjC 教程中,我们常对从C++或Java 或其他面向对象语言转过来的程序员说,ObjC 中的方法调用(ObjC中的术语为消息)跟其他语言中的方法调用差不多,只是形式有些不同而已。  ...
  • chaoyuan899
  • chaoyuan899
  • 2014年06月18日 11:35
  • 534

[Cocoa]深入浅出 Cocoa 之 Plugin

深入浅出 Cocoa 之 Plugin 罗朝辉(http://blog.csdn.net/kesalin) CC 许可,转载请注明出处 在前文 深入浅出 Cocoa 之 Framework中讲解...
  • kesalin
  • kesalin
  • 2011年10月28日 13:35
  • 4321

iOS 开发 网络编程详解之Socket详解

Socket基本概念 建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装. Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须...
  • kuangdacaikuang
  • kuangdacaikuang
  • 2016年11月29日 01:27
  • 3936

深入浅出Cocoa之Framework

Mac OS X 扩展了 framework 的功能,让我们能够利用它来共享代码和资源。framework 在概念上有点像 Window 下的库,但是比库更加强大。 Framework 简介...
  • cwq9944
  • cwq9944
  • 2012年05月21日 09:59
  • 779

深入浅出Cocoa之类与对象

最近打算写一些ObjC中比较底层的东西,尤其是 runtime 相关的。苹果已经将 ObjC runtime 代码开源了,我们可以从:http://opensource.apple.com/sourc...
  • war3pc
  • war3pc
  • 2014年01月03日 17:14
  • 539

深入浅出 Cocoa 之 Framework

[Cocoa]深入浅出 Cocoa 之 Framework 罗朝辉(http://blog.csdn.net/kesalin/) CC许可,转载请注明出处 Framework 简...
  • leonpengweicn
  • leonpengweicn
  • 2011年11月05日 11:02
  • 897
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:[深入浅出Cocoa]iOS网络编程之NSStream
举报原因:
原因补充:

(最多只允许输入30个字)