[深入淺出Cocoa]iOS網(wǎng)絡(luò)編程之NSStream
一占哟,NSStream簡介
首先來回顧下。在前文《
[深入淺出Cocoa]iOS網(wǎng)絡(luò)編程之Socket
》中翩迈,提到iOS網(wǎng)絡(luò)編程層次模型分為三層:
- Cocoa層:NSURL持灰,Bonjour,Game Kit帽馋,WebKit
- Core Foundation層:基于 C 的?CFNetwork 和 CFNetServices
- OS層:基于 C 的 BSD socket
前文《
》?和《
》?講了最底層的 socket 和Core Foundation層的 CFNetwork搅方,本文將介紹位于 Cocoa 中的 NSStream。NSStream 其實(shí)只是用 Objective-C 對(duì) CFNetwork 的簡單封裝绽族,它使用名為?NSStreamDelegate 的協(xié)議來實(shí)現(xiàn) CFNetwork 中的回調(diào)函數(shù)的作用姨涡,同樣,runloop 也與 NSStream 結(jié)合的很好吧慢。NSStream 有兩個(gè)實(shí)體類:NSInputStream 和 NSOutputStream涛漂,分別對(duì)應(yīng) CFNetwork 中的?CFReadStream 和?CFWriteStream。
本文示例代碼請(qǐng)查看:
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 類似匈仗,如打開,關(guān)閉逢慌,獲取狀態(tài)和錯(cuò)誤信息悠轩,以及和 runloop 結(jié)合等在這里就不再重復(fù)了。前面提到 NSStream 是通過 NSStreamDelegate 來實(shí)現(xiàn) CFNetwork 中的回調(diào)函數(shù)攻泼,這個(gè)可選的協(xié)議只有一個(gè)接口:
- (void)stream:(NSStream?*)aStream handleEvent:(NSStreamEvent)eventCode;
NSStreamEvent 是一個(gè)流事件枚舉:
typedef?NS_OPTIONS(NSUInteger, NSStreamEvent) {
? ? NSStreamEventNone =?0,
? ? NSStreamEventOpenCompleted =?1UL <<?0,
? ? NSStreamEventHasBytesAvailable =?1UL <<?1,
? ? NSStreamEventHasSpaceAvailable =?1UL <<?2,
? ? NSStreamEventErrorOccurred =?1UL <<?3,
? ? NSStreamEventEndEncountered =?1UL <<?4
};
這些事件枚舉的含義也和 CFNetwork 中的?CFStreamEventType 類似火架,在此也就不再重復(fù)了。
NSInputStream?類有如下接口:
- (NSInteger)read:(uint8_t?*)buffer maxLength:(NSUInteger)len;
從流中讀取數(shù)據(jù)到 buffer 中忙菠,buffer 的長度不應(yīng)少于 len何鸡,該接口返回實(shí)際讀取的數(shù)據(jù)長度(該長度最大為 len)。
- (BOOL)getBuffer:(uint8_t?**)buffer length:(NSUInteger?*)len;
獲取當(dāng)前流中的數(shù)據(jù)以及大小牛欢,注意 buffer 只在下一個(gè)流操作之前有效骡男。
- (BOOL)hasBytesAvailable;
檢查流中是否還有數(shù)據(jù)。
NSOutputStream?類有如下接口:
- (NSInteger)write:(const?uint8_t?*)buffer maxLength:(NSUInteger)len;
將 buffer 中的數(shù)據(jù)寫入流中傍睹,返回實(shí)際寫入的字節(jié)數(shù)隔盛。
- (BOOL)hasSpaceAvailable;
檢查流中是否還有可供寫入的空間犹菱。
從這些接口可以看出,NSStream 真的就是 CFNetwork 上的一層簡單的 Objective-C 封裝骚亿。但 iOS 中的 NSStream 不支持 NShost已亥,這是一個(gè)缺陷,蘋果也意識(shí)到這問題了(http://developer.apple.com/library/ios/#qa/qa1652/_index.html)来屠,我們可以通過 NSStream 的擴(kuò)展函數(shù)來實(shí)現(xiàn)該功能:
@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
三虑椎,客戶端示例代碼
與前面的示例類似,在這里我只演示客戶端示例俱笛。同樣捆姜,我們也在一個(gè)后臺(tái)線程中啟動(dòng)網(wǎng)絡(luò)操作:
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]]; NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(loadDataFromServerWithURL:) object:url]; [backgroundThread start];
然后在?loadDataFromServerWithURL 中創(chuàng)建 NSInputStream,并設(shè)置其 delegate迎膜,將其加入到 run-loop 的事件源中泥技,然后打開流,運(yùn)行 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]; }
因?yàn)槲覀儗?KSNSStreamViewController 當(dāng)作 NSInputStream 的 delegate磕仅,因此要在 KSNSStreamViewController 中實(shí)現(xiàn)該 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; } }
當(dāng)數(shù)據(jù)讀取完畢或者讀取失敗時(shí)珊豹,調(diào)用 cleanUpStream 方法來關(guān)閉流:
- (void)cleanUpStream:(NSStream *)stream { [stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [stream close]; stream = nil; }