iOS中流(NSStream)的使用

流提供了一種簡單的方式在不同和介質中交換數據,這種交換方式是與設備無關的。流是在通信路徑中串行傳輸的連續的比特位序列。從編碼的角度來看,流是單向的,因此流可以是輸入流或輸出流。除了基于文件的流外,其它形式的流都是不可查找的,這些流的數據一旦消耗完后,就無法從流對象中再次獲取。

在Cocoa中包含三個與流相關的類:NSStream、NSInputStream和NSOutputStream。NSStream是一個抽象基類,定義了所有流對象的基礎接口和屬性。NSInputStream和NSOutputStream繼承自NSStream,實現了輸入流和輸出流的默認行為。下圖描述了流的應用場景:

從圖中看,NSInputStream可以從文件、socket和NSData對象中獲取數據;NSOutputStream可以將數據寫入文件、socket、內存緩存和NSData對象中。這三處類主要處理一些比較底層的任務。

流對象有一些相關的屬性。大部分屬性是用于處理網絡安全和配置的,這些屬性統稱為SSL和SOCKS代理信息。兩個比較重要的屬性是:

NSStreamDataWrittenToMemoryStreamKey:允許輸出流查詢寫入到內存的數據
NSStreamFileCurrentOffsetKey:允許操作基于文件的流的讀寫位置
可以給流對象指定一個代理對象。如果沒有指定,則流對象作為自己的代理。流對象調用唯一的代理方法stream:handleEvent:來處理流相關的事件:

對于輸入流來說,是有可用的數據可讀取事件。我們可以使用read:maxLength:方法從流中獲取數據
對于輸出流來說,是準備好寫入的數據事件。我們可以使用write:maxLength:方法將數據寫入流
Cocoa中的流對象與Core Foundation中的流對象是對應的。我們可以通過toll-free橋接方法來進行相互轉換。NSStream、NSInputStream和NSOutputStream分別對應CFStream、CFReadStream和CFWriteStream。但這兩者間不是完全一樣的。Core Foundation一般使用回調函數來處理數據。另外我們可以子類化NSStream、NSInputStream和NSOutputStream,來自定義一些屬性和行為,而Core Foundation中的流對象則無法進行擴展。

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

從輸入流中讀取數據

從一個NSInputStream流中讀取數據主要包括以下幾個步驟:

從數據源中創建和初始化一個NSInputStream實例
將流對象放入一個run loop中并打開流
處理流對象發送到其代理的事件
當沒有更多數據可讀取時,關閉并銷毀流對象。
準備流對象

要使用一個NSInputStream,必須要有數據源。數據源可以是文件、NSData對象和網絡socket。創建好后,我們設置其代理對象,并將其放入到run loop中,然后打開流。代碼清單1展示了這個準備過程.

代理清單1

- (void)setUpStreamForFile:(NSString *)path{
NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:path];
inputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open]; }

在流對象放入run loop且有流事件(有可讀數據)發生時,流對象會向代理對象發送stream:handleEvent:消息。在打開流之前,我們需要調用流對象的scheduleInRunLoop:forMode:方法,這樣做可以避免在沒有數據可讀時阻塞代理對象的操作。我們需要確保的是流對象被放入正確的run loop中,即放入流事件發生的那個線程的run loop中。

處理流事件

打開流后,我們可以使用streamStatus屬性查看流的狀態,用hasBytesAvailable屬性檢測是否有可讀的數據,用streamError來查看流處理過程中產生的錯誤。

流一旦打開后,將會持續發送stream:handleEvent:消息給代理對象,直到流結束為止。這個消息接收一個NSStreamEvent常量作為參數,以標識事件的類型。對于NSInputStream對象,主要的事件類型包括NSStreamEventOpenCompleted、NSStreamEventHasBytesAvailable和NSStreamEventEndEncountered。通常我們會對NSStreamEventHasBytesAvailable更感興趣。代理清單2演示了從流中獲取數據的過程

代理清單2

- (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];  // 讀取數據
            if (len) {
                [data appendBytes:(const void *)buf length:len];
            }
        }
            break;
    }
}

當NSInputStream在處理流的過程中出現錯誤時,它將停止流處理并產生一個NSStreamEventErrorOccurred事件給代理。我們同樣的上面的代理方法中來處理這個事件。

清理流對象

當NSInputStream讀取到流的結尾時,會發送一個NSStreamEventEndEncountered事件給代理,代理對象應該銷毀流對象,此過程應該與創建時相對應,代碼清單3演示了關閉和釋放流對象的過程。

代理清單3

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventEndEncountered:
        {
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
            aStream = nil;
        }
            break;
    }
}

寫入數據到輸出流

類似于從輸入流讀取數據,寫入數據到輸出流時,需要下面幾個步驟:

使用要寫入的數據創建和初始化一個NSOutputStream實例,并設置代理對象
將流對象放到run loop中并打開流
處理流對象發送到代理對象中的事件
如果流對象寫入數據到內存,則通過請求NSStreamDataWrittenToMemoryStreamKey屬性來獲取數據
當沒有更多數據可供寫入時,處理流對象
基本流程與輸入流的讀取差不多,我們主要介紹不同的地方

數據可寫入的位置包括文件、C緩存、程序內存和網絡socket。
hasSpaceAvailable屬性表示是否有空間來寫入數據
在stream:handleEvent:中主要處理NSStreamEventHasSpaceAvailable事件,并調用流的write:maxLength方法寫數據。代碼清單4演示了這一過程。
如果NSOutputStream對象的目標是應用的內存時,在NSStreamEventEndEncountered事件中可能需要從內存中獲取流中的數據。我們將調用NSOutputStream對象的propertyForKey:的屬性,并指定key為NSStreamDataWrittenToMemoryStreamKey來獲取這些數據。
代理清單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;
        }
    }
}

這里需要注意的是:當代理接收到NSStreamEventHasSpaceAvailable事件而沒有寫入任何數據到流時,代理將不再從run loop中接收該事件,直到NSOutputStream對象接收到更多數據,這時run loop會重啟NSStreamEventHasSpaceAvailable事件。

流的輪循處理

在流的處理過程中,除了將流放入run loop來處理流事件外,還可以對流進行輪循處理。我們將流處理數據的過程放到一個循環中,并在循環中不斷地去詢問流是否有可用的數據供讀取(hasBytesAvailable)或可用的空間供寫入(hasSpaceAvailable)。當處理到流的結尾時,我們跳出循環結束流的操作。

具體的過程如代碼清單5所示

代碼清單5

 - (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;}

這種處理方法的問題在于它會阻塞當前線程,直到流處理結束為止,才繼續進行后面的操作。而這種問題在處理網絡socket流時尤為嚴重,我們必須等待服務端數據回來后才能繼續操作。因此,通常情況下,建議使用run loop方式來處理流事件。

錯誤處理

當流出現錯誤時,會停止對流數據的處理。一個流對象在出現錯誤時,不能再用于讀或寫操作,雖然在關閉前可以查詢它的狀態。

NSStream和NSOutputStream類會以幾種方式來告知錯誤的發生:

如果流被放到run loop中,對象會發送一個NSStreamEventErrorOccurred事件到代理對象的stream:handleEvent:方法中
任何時候,可以調用streamStatus屬性來查看是否發生錯誤(返回NSStreamStatusError)
如果在通過調用write:maxLength:寫入數據到NSOutputStream對象時返回-1,則發生一個寫錯誤。
一旦確定產生錯誤時,我們可以調用流對象的streamError屬性來查看錯誤的詳細信息。在此我們不再舉例。

設置Socket流

在iOS中,NSStream類不支持連接到遠程主機,幸運的是CFStream支持。前面已經說過這兩者可以通過toll-free橋接來相互轉換。使用CFStream時,我們可以調用CFStreamCreatePairWithSocketToHost函數并傳遞主機名和端口號,來獲取一個CFReadStreamRef和一個CFWriteStreamRef來進行通信,然后我們可以將它們轉換為NSInputStream和NSOutputStream對象來處理。

具體的處理流程我們會在后期詳細討論。

參考

Stream Programming Guide

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • 流提供了一種簡單的方式在不同和介質中交換數據,這種交換方式是與設備無關的。流是在通信路徑中串行傳輸的連續的比特位序...
    每天刷兩次牙閱讀 2,778評論 2 1
  • 流提供了一種簡單的方式在不同和介質中交換數據,這種交換方式是與設備無關的。流是在通信路徑中串行傳輸的連續的比特位序...
    小魚兒喜歡花無缺閱讀 1,933評論 1 2
  • 流是位數據通過通信路徑的連續傳送序列。它是單向的,從一個應用程序的角度,流可以是輸入流(讀操作流)或者輸出流(寫操...
    星捷閱讀 1,184評論 0 2
  • 國家電網公司企業標準(Q/GDW)- 面向對象的用電信息數據交換協議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 11,049評論 6 13
  • 概述:互聯網金融用創新的模式 ,徹底顛覆了人們的財富觀念,錢生錢的理念深入人心,大大普及了理財知識,創造了海量的用...
    2ee6980a78cf閱讀 345評論 0 0