[深刻淺出Cocoa]iOS網絡編程之NSStream

 

[深刻淺出Cocoa]iOS網絡編程之NSStream


目錄(?)[+]html

[深刻淺出Cocoa]iOS網絡編程之NSStreamios

羅朝輝 (http://blog.csdn.net/kesalin/)git

本文遵循「署名-非商業用途-保持一致」創做公用協議github

 

一,NSStream簡介

首先來回顧下。在前文《[深刻淺出Cocoa]iOS網絡編程之Socket》中,提到iOS網絡編程層次模型分爲三層:編程

  • Cocoa層:NSURL,Bonjour,Game Kit,WebKit網絡

  • Core Foundation層:基於 C 的 CFNetwork 和 CFNetServicesapp

  • OS層:基於 C 的 BSD socketsocket

前文《iOS網絡編程之Socket》 和《iOS網絡編程之CFNetwork》 講了最底層的 socket 和Core Foundation層的 CFNetwork,本文將介紹位於 Cocoa 中的 NSStream。NSStream 其實只是用 Objective-C 對 CFNetwork 的簡單封裝,它使用名爲 NSStreamDelegate 的協議來實現 CFNetwork 中的回調函數的做用,一樣,runloop 也與 NSStream 結合的很好。NSStream 有兩個實體類:NSInputStream 和 NSOutputStream,分別對應 CFNetwork 中的 CFReadStream 和 CFWriteStream。函數

 

本文示例代碼請查看:oop

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 編程。

相關文章
相關標籤/搜索