【轉】iOS中流(Stream)的使用

來源:互聯網
上載者:User

標籤:重要   串列   轉換   線程   let   位置   ring   檢測   通過   

轉自:http://southpeak.github.io/blog/2014/07/17/ioszhong-liu-stream-de-shi-yong/

流提供了一種簡單的方式在不同和介質中交換資料,這種交換方式是與裝置無關的。流是在通訊路徑中串列傳輸的連續的位元位序列。從編碼的角度來看,流是單向的,因此流可以是輸入資料流或輸出資料流。除了基於檔案的流外,其它形式的流都是不可尋找的,這些流的資料一旦消耗完後,就無法從流對象中再次擷取。

在Cocoa中包含三個與流相關的類:NSStream、NSInputStream和NSOutputStream。NSStream是一個抽象基類,定義了所有流對象的基礎介面和屬性。NSInputStream和NSOutputStream繼承自NSStream,實現了輸入資料流和輸出資料流的預設行為。描述了流的應用情境:



看,NSInputStream可以從檔案、socket和NSData對象中擷取資料;NSOutputStream可以將資料寫入檔案、socket、記憶體緩衝和NSData對象中。這三處類主要處理一些比較底層的任務。

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

  1. NSStreamDataWrittenToMemoryStreamKey:允許輸出資料流查詢寫入到記憶體的資料
    NSStreamFileCurrentOffsetKey:允許操作基於檔案的流的讀寫位置



可以給流對象指定一個代理對象。如果沒有指定,則流對象作為自己的代理。流對象調用唯一的代理方法stream:handleEvent:來處理流相關的事件:

  1. 對於輸入資料流來說,是有可用的資料可讀取事件。我們可以使用read:maxLength:方法從流中擷取資料
    對於輸出資料流來說,是準備好寫入的資料事件。我們可以使用write:maxLength:方法將資料寫入流



Cocoa中的流對象與Core Foundation中的流對象是對應的。我們可以通過toll-free橋接方法來進行相互轉換。NSStream、NSInputStream和NSOutputStream分別對應CFStream、CFReadStream和CFWriteStream。但這兩者間不是完全一樣的。Core Foundation一般使用回呼函數來處理資料。另外我們可以子類化NSStream、NSInputStream和NSOutputStream,來自訂一些屬性和行為,而Core Foundation中的流對象則無法進行擴充。

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

從輸入資料流中讀取資料

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

  1. 從資料來源中建立和初始化一個NSInputStream執行個體
    將流對象放入一個run loop中並開啟流
    處理流對象發送到其代理的事件
    當沒有更多資料可讀取時,關閉並銷毀流對象。
    準備流對象



要使用一個NSInputStream,必須要有資料來源。資料來源可以是檔案、NSData對象和網路socket。建立好後,我們設定其代理對象,並將其放入到run loop中,然後開啟流。代碼清單1展示了這個準備過程.

代理清單1

 複製代碼
  1. - (void)setUpStreamForFile:(NSString *)path
  2. {
  3.     NSInputStream *inputStream = [[NSInputStream alloc] initWithFileAtPath:path];
  4.     inputStream.delegate = self;
  5.     [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  6.     [inputStream open];
  7. }



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

處理流事件

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

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

代理清單2

 複製代碼
  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  2. {
  3.     switch (eventCode) {
  4.         case NSStreamEventHasBytesAvailable:
  5.         {
  6.             if (!data) {
  7.                 data = [NSMutableData data];
  8.             }
  9.             uint8_t buf[1024];
  10.             unsigned int len = 0;
  11.             len = [(NSInputStream *)aStream read:buf maxLength:1024];  // 讀取資料
  12.             if (len) {
  13.                 [data appendBytes:(const void *)buf length:len];
  14.             }
  15.         }
  16.             break;
  17.     }
  18. }



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

清理流對象

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

代理清單3

 複製代碼
  1. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
  2. {
  3.     switch (eventCode) {
  4.         case NSStreamEventEndEncountered:
  5.         {
  6.             [aStream close];
  7.             [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
  8.             aStream = nil;
  9.         }
  10.             break;
  11.     }
  12. }



寫入資料到輸出資料流

類似於從輸入資料流讀取資料,寫入資料到輸出資料流時,需要下面幾個步驟:

  1. 使用要寫入的資料建立和初始化一個NSOutputStream執行個體,並設定代理對象
    將流對象放到run loop中並開啟流
    處理流對象發送到代理對象中的事件
    如果流對象寫入資料到記憶體,則通過請求NSStreamDataWrittenToMemoryStreamKey屬性來擷取資料
    當沒有更多資料可供寫入時,處理流對象



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

資料可寫入的位置包括檔案、C緩衝、程式記憶體和網路socket。

  1. 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

 複製代碼
  1. - (void)createNewFile {
  2.     NSOutputStream *oStream = [[NSOutputStream alloc] initToMemory];
  3.     [oStream open];
  4.     ???uint8_t *readBytes = (uint8_t *)[data mutableBytes];
  5.     uint8_t buf[1024];
  6.     int len = 1024;
  7.     while (1) {
  8.         if (len == 0) break;
  9.         if ([oStream hasSpaceAvailable])
  10.         {
  11.             (void)strncpy(buf, readBytes, len);
  12.             readBytes += len;
  13.             if ([oStream write:(const uint8_t *)buf maxLength:len] == -1)
  14.             {
  15.                 [self handleError:[oStream streamError]];
  16.                 break;
  17.             }
  18.             [bytesWritten setIntValue:[bytesWritten intValue]+len];
  19.             len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 : [data length] - [bytesWritten intValue]);
  20.         }
  21.     }
  22.     NSData *newData = [oStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
  23.     if (!newData) {
  24.         NSLog(@"No data written to memory!");
  25.     } else {
  26.         [self processData:newData];
  27.     }
  28.     [oStream close];
  29.     [oStream release];
  30.     oStream = nil;
  31. }



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

錯誤處理

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

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

 複製代碼
  1. 如果流被放到run loop中,對象會發送一個NSStreamEventErrorOccurred事件到代理對象的stream:handleEvent:方法中
  2. 任何時候,可以調用streamStatus屬性來查看是否發生錯誤(返回NSStreamStatusError)
  3. 如果在通過調用write:maxLength:寫入資料到NSOutputStream對象時返回-1,則發生一個寫錯誤。



一旦確定產生錯誤時,我們可以調用流對象的streamError屬性來查看錯誤的詳細資料。在此我們不再舉例。

設定Socket流

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

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

參考

Stream Programming Guide

【轉】iOS中流(Stream)的使用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.