iOS 中流(Stream)的使用

流提供了一種簡(jiǎn)單的方式在不同和介質(zhì)中交換數(shù)據(jù),這種交換方式是與設(shè)備無(wú)關(guān)的。流是在通信路徑中串行傳輸?shù)倪B續(xù)的比特位序列。從編碼的角度來(lái)看,流是單向的,因此流可以是輸入流或輸出流。除了基于文件的流外,其它形式的流都是不可查找的,這些流的數(shù)據(jù)一旦消耗完后,就無(wú)法從流對(duì)象中再次獲取。

在Cocoa中包含三個(gè)與流相關(guān)的類:NSStream、NSInputStream和NSOutputStream。NSStream是一個(gè)抽象基類,定義了所有流對(duì)象的基礎(chǔ)接口和屬性。NSInputStream和NSOutputStream繼承自NSStream,實(shí)現(xiàn)了輸入流和輸出流的默認(rèn)行為。下圖描述了流的應(yīng)用場(chǎng)景:

從圖中看,NSInputStream可以從文件、socket和NSData對(duì)象中獲取數(shù)據(jù);NSOutputStream可以將數(shù)據(jù)寫(xiě)入文件、socket、內(nèi)存緩存和NSData對(duì)象中。這三處類主要處理一些比較底層的任務(wù)。

流對(duì)象有一些相關(guān)的屬性。大部分屬性是用于處理網(wǎng)絡(luò)安全和配置的,這些屬性統(tǒng)稱為SSL和SOCKS代理信息。兩個(gè)比較重要的屬性是:

NSStreamDataWrittenToMemoryStreamKey:允許輸出流查詢寫(xiě)入到內(nèi)存的數(shù)據(jù)

NSStreamFileCurrentOffsetKey:允許操作基于文件的流的讀寫(xiě)位置

可以給流對(duì)象指定一個(gè)代理對(duì)象。如果沒(méi)有指定,則流對(duì)象作為自己的代理。流對(duì)象調(diào)用唯一的代理方法stream:handleEvent:來(lái)處理流相關(guān)的事件:

對(duì)于輸入流來(lái)說(shuō),是有可用的數(shù)據(jù)可讀取事件。我們可以使用read:maxLength:方法從流中獲取數(shù)據(jù)

對(duì)于輸出流來(lái)說(shuō),是準(zhǔn)備好寫(xiě)入的數(shù)據(jù)事件。我們可以使用write:maxLength:方法將數(shù)據(jù)寫(xiě)入流

Cocoa中的流對(duì)象與Core Foundation中的流對(duì)象是對(duì)應(yīng)的。我們可以通過(guò)toll-free橋接方法來(lái)進(jìn)行相互轉(zhuǎn)換。NSStream、NSInputStream和NSOutputStream分別對(duì)應(yīng)CFStream、CFReadStream和CFWriteStream。但這兩者間不是完全一樣的。Core Foundation一般使用回調(diào)函數(shù)來(lái)處理數(shù)據(jù)。另外我們可以子類化NSStream、NSInputStream和NSOutputStream,來(lái)自定義一些屬性和行為,而Core Foundation中的流對(duì)象則無(wú)法進(jìn)行擴(kuò)展。

上面主要介紹了iOS中流的一些基本概念,我們下面將介紹流的具體使用,首先看看如何從流中讀取數(shù)據(jù)。

從輸入流中讀取數(shù)據(jù)

從一個(gè)NSInputStream流中讀取數(shù)據(jù)主要包括以下幾個(gè)步驟:

從數(shù)據(jù)源中創(chuàng)建和初始化一個(gè)NSInputStream實(shí)例

將流對(duì)象放入一個(gè)run loop中并打開(kāi)流

處理流對(duì)象發(fā)送到其代理的事件

當(dāng)沒(méi)有更多數(shù)據(jù)可讀取時(shí),關(guān)閉并銷毀流對(duì)象。

準(zhǔn)備流對(duì)象

要使用一個(gè)NSInputStream,必須要有數(shù)據(jù)源。數(shù)據(jù)源可以是文件、NSData對(duì)象和網(wǎng)絡(luò)socket。創(chuàng)建好后,我們?cè)O(shè)置其代理對(duì)象,并將其放入到run loop中,然后打開(kāi)流。代碼清單1展示了這個(gè)準(zhǔn)備過(guò)程.

代理清單1

復(fù)制代碼

- (void)setUpStreamForFile:(NSString *)path

{

NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:path];

inputStream.delegate = self;

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

[inputStream open];

}

在流對(duì)象放入run loop且有流事件(有可讀數(shù)據(jù))發(fā)生時(shí),流對(duì)象會(huì)向代理對(duì)象發(fā)送stream:handleEvent:消息。在打開(kāi)流之前,我們需要調(diào)用流對(duì)象的scheduleInRunLoop:forMode:方法,這樣做可以避免在沒(méi)有數(shù)據(jù)可讀時(shí)阻塞代理對(duì)象的操作。我們需要確保的是流對(duì)象被放入正確的run loop中,即放入流事件發(fā)生的那個(gè)線程的run loop中。

處理流事件

打開(kāi)流后,我們可以使用streamStatus屬性查看流的狀態(tài),用hasBytesAvailable屬性檢測(cè)是否有可讀的數(shù)據(jù),用streamError來(lái)查看流處理過(guò)程中產(chǎn)生的錯(cuò)誤。

流一旦打開(kāi)后,將會(huì)持續(xù)發(fā)送stream:handleEvent:消息給代理對(duì)象,直到流結(jié)束為止。這個(gè)消息接收一個(gè)NSStreamEvent常量作為參數(shù),以標(biāo)識(shí)事件的類型。對(duì)于NSInputStream對(duì)象,主要的事件類型包括NSStreamEventOpenCompleted、NSStreamEventHasBytesAvailable和NSStreamEventEndEncountered。通常我們會(huì)對(duì)NSStreamEventHasBytesAvailable更感興趣。代理清單2演示了從流中獲取數(shù)據(jù)的過(guò)程

代理清單2

復(fù)制代碼

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

{

switch (eventCode) {

case NSStreamEventHasBytesAvailable:

{

if (!data) {

data = [NSMutableData data];

}

uint8_t buf[1024];

unsigned int len = 0;

len = [(NSInputStream *)aStream read:buf maxLength:1024];??// 讀取數(shù)據(jù)

if (len) {

[data appendBytes:(const void *)buf length:len];

}

}

break;

}

}

當(dāng)NSInputStream在處理流的過(guò)程中出現(xiàn)錯(cuò)誤時(shí),它將停止流處理并產(chǎn)生一個(gè)NSStreamEventErrorOccurred事件給代理。我們同樣的上面的代理方法中來(lái)處理這個(gè)事件。

清理流對(duì)象

當(dāng)NSInputStream讀取到流的結(jié)尾時(shí),會(huì)發(fā)送一個(gè)NSStreamEventEndEncountered事件給代理,代理對(duì)象應(yīng)該銷毀流對(duì)象,此過(guò)程應(yīng)該與創(chuàng)建時(shí)相對(duì)應(yīng),代碼清單3演示了關(guān)閉和釋放流對(duì)象的過(guò)程。

代理清單3

復(fù)制代碼

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

{

switch (eventCode) {

case NSStreamEventEndEncountered:

{

[aStream close];

[aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

aStream = nil;

}

break;

}

}

寫(xiě)入數(shù)據(jù)到輸出流

類似于從輸入流讀取數(shù)據(jù),寫(xiě)入數(shù)據(jù)到輸出流時(shí),需要下面幾個(gè)步驟:

使用要寫(xiě)入的數(shù)據(jù)創(chuàng)建和初始化一個(gè)NSOutputStream實(shí)例,并設(shè)置代理對(duì)象

將流對(duì)象放到run loop中并打開(kāi)流

處理流對(duì)象發(fā)送到代理對(duì)象中的事件

如果流對(duì)象寫(xiě)入數(shù)據(jù)到內(nèi)存,則通過(guò)請(qǐng)求NSStreamDataWrittenToMemoryStreamKey屬性來(lái)獲取數(shù)據(jù)

當(dāng)沒(méi)有更多數(shù)據(jù)可供寫(xiě)入時(shí),處理流對(duì)象

基本流程與輸入流的讀取差不多,我們主要介紹不同的地方

數(shù)據(jù)可寫(xiě)入的位置包括文件、C緩存、程序內(nèi)存和網(wǎng)絡(luò)socket。

hasSpaceAvailable屬性表示是否有空間來(lái)寫(xiě)入數(shù)據(jù)

在stream:handleEvent:中主要處理NSStreamEventHasSpaceAvailable事件,并調(diào)用流的write:maxLength方法寫(xiě)數(shù)據(jù)。代碼清單4演示了這一過(guò)程。

如果NSOutputStream對(duì)象的目標(biāo)是應(yīng)用的內(nèi)存時(shí),在NSStreamEventEndEncountered事件中可能需要從內(nèi)存中獲取流中的數(shù)據(jù)。我們將調(diào)用NSOutputStream對(duì)象的propertyForKey:的屬性,并指定key為NSStreamDataWrittenToMemoryStreamKey來(lái)獲取這些數(shù)據(jù)。

代理清單4

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

{

switch (eventCode) {

case NSStreamEventHasSpaceAvailable:

{

uint8_t *readBytes = (uint8_t *)[data mutableBytes];

readBytes += byteIndex;

int data_len = [data length];

unsigned int len = (data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex);

uint8_t buf[len];

(void)memcpy(buf, readBytes, len);

len = [aStream write:(const uint_8 *)buf maxLength:len];

byteIndex += len;

break;

}

}

}

這里需要注意的是:當(dāng)代理接收到NSStreamEventHasSpaceAvailable事件而沒(méi)有寫(xiě)入任何數(shù)據(jù)到流時(shí),代理將不再?gòu)膔un loop中接收該事件,直到NSOutputStream對(duì)象接收到更多數(shù)據(jù),這時(shí)run loop會(huì)重啟NSStreamEventHasSpaceAvailable事件。

流的輪循處理

在流的處理過(guò)程中,除了將流放入run loop來(lái)處理流事件外,還可以對(duì)流進(jìn)行輪循處理。我們將流處理數(shù)據(jù)的過(guò)程放到一個(gè)循環(huán)中,并在循環(huán)中不斷地去詢問(wèn)流是否有可用的數(shù)據(jù)供讀取(hasBytesAvailable)或可用的空間供寫(xiě)入(hasSpaceAvailable)。當(dāng)處理到流的結(jié)尾時(shí),我們跳出循環(huán)結(jié)束流的操作。

具體的過(guò)程如代碼清單5所示

代碼清單5

復(fù)制代碼

- (void)createNewFile {

NSOutputStream *oStream = [[NSOutputStream alloc] initToMemory];

[oStream open];

uint8_t *readBytes = (uint8_t *)[data mutableBytes];

uint8_t buf[1024];

int len = 1024;

while (1) {

if (len == 0) break;

if ([oStream hasSpaceAvailable])

{

(void)strncpy(buf, readBytes, len);

readBytes += len;

if ([oStream write:(const uint8_t *)buf maxLength:len] == -1)

{

[self handleError:[oStream streamError]];

break;

}

[bytesWritten setIntValue:[bytesWritten intValue]+len];

len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 : [data length] - [bytesWritten intValue]);

}

}

NSData *newData = [oStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

if (!newData) {

NSLog(@"No data written to memory!");

} else {

[self processData:newData];

}

[oStream close];

[oStream release];

oStream = nil;

}

這種處理方法的問(wèn)題在于它會(huì)阻塞當(dāng)前線程,直到流處理結(jié)束為止,才繼續(xù)進(jìn)行后面的操作。而這種問(wèn)題在處理網(wǎng)絡(luò)socket流時(shí)尤為嚴(yán)重,我們必須等待服務(wù)端數(shù)據(jù)回來(lái)后才能繼續(xù)操作。因此,通常情況下,建議使用run loop方式來(lái)處理流事件。

錯(cuò)誤處理

當(dāng)流出現(xiàn)錯(cuò)誤時(shí),會(huì)停止對(duì)流數(shù)據(jù)的處理。一個(gè)流對(duì)象在出現(xiàn)錯(cuò)誤時(shí),不能再用于讀或?qū)懖僮?,雖然在關(guān)閉前可以查詢它的狀態(tài)。

NSStream和NSOutputStream類會(huì)以幾種方式來(lái)告知錯(cuò)誤的發(fā)生:

復(fù)制代碼

如果流被放到run loop中,對(duì)象會(huì)發(fā)送一個(gè)NSStreamEventErrorOccurred事件到代理對(duì)象的stream:handleEvent:方法中

任何時(shí)候,可以調(diào)用streamStatus屬性來(lái)查看是否發(fā)生錯(cuò)誤(返回NSStreamStatusError)

如果在通過(guò)調(diào)用write:maxLength:寫(xiě)入數(shù)據(jù)到NSOutputStream對(duì)象時(shí)返回-1,則發(fā)生一個(gè)寫(xiě)錯(cuò)誤。

一旦確定產(chǎn)生錯(cuò)誤時(shí),我們可以調(diào)用流對(duì)象的streamError屬性來(lái)查看錯(cuò)誤的詳細(xì)信息。在此我們不再舉例。

設(shè)置Socket流

在iOS中,NSStream類不支持連接到遠(yuǎn)程主機(jī),幸運(yùn)的是CFStream支持。前面已經(jīng)說(shuō)過(guò)這兩者可以通過(guò)toll-free橋接來(lái)相互轉(zhuǎn)換。使用CFStream時(shí),我們可以調(diào)用CFStreamCreatePairWithSocketToHost函數(shù)并傳遞主機(jī)名和端口號(hào),來(lái)獲取一個(gè)CFReadStreamRef和一個(gè)CFWriteStreamRef來(lái)進(jìn)行通信,然后我們可以將它們轉(zhuǎn)換為NSInputStream和NSOutputStream對(duì)象來(lái)處理。

具體的處理流程我們會(huì)在后期詳細(xì)討論。

參考

Stream Programming Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 流提供了一種簡(jiǎn)單的方式在不同和介質(zhì)中交換數(shù)據(jù),這種交換方式是與設(shè)備無(wú)關(guān)的。流是在通信路徑中串行傳輸?shù)倪B續(xù)的比特位序...
    磁針石閱讀 15,104評(píng)論 8 45
  • 流提供了一種簡(jiǎn)單的方式在不同和介質(zhì)中交換數(shù)據(jù),這種交換方式是與設(shè)備無(wú)關(guān)的。流是在通信路徑中串行傳輸?shù)倪B續(xù)的比特位序...
    小魚(yú)兒喜歡花無(wú)缺閱讀 2,012評(píng)論 1 2
  • 流是位數(shù)據(jù)通過(guò)通信路徑的連續(xù)傳送序列。它是單向的,從一個(gè)應(yīng)用程序的角度,流可以是輸入流(讀操作流)或者輸出流(寫(xiě)操...
    星捷閱讀 1,224評(píng)論 0 2
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,441評(píng)論 6 13
  • 推薦一款十分鐘快手早餐 - 涼拌意大利面 材料 意大利面 玉米粒 青豆 胡蘿卜絲 黃瓜 蕃茄醬 制作方法: 意大利...
    子莯青青閱讀 745評(píng)論 0 0

友情鏈接更多精彩內(nèi)容