[深入淺出Cocoa]iOS網路編程之NSStream

來源:互聯網
上載者:User
文章目錄
  • 四,結語

[深入淺出Cocoa]iOS網路編程之NSStream

羅朝輝 (http://www.cnblogs.com/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 編程。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.