2013-03-11 19 views
12

(更新)這是一個簡單的問題:在iOS中我想讀一個大文件,對它做一些處理(在這種情況下編碼爲Base64 string()和保存到設備上的臨時文件。我建立了一個NSInputStream從文件中讀取,然後在NSInputStream停止運行,有時會拋出EXC_BAD_ACCESS

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

我做的大部分工作。出於某種原因,有時候我可以看到NSInputStream只是停止我知道,因爲我有一條線

NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode); 

(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode開始,有時我只想看到輸出

stream <__NSCFInputStream: 0x1f020b00> got event 2 

(對應於事件NSStreamEventHasBytesAvailable)然後就沒有後來。不是事件10,它對應於NSStreamEventEndEncountered,不是一個錯誤事件,沒有!有時候我甚至會得到一個EXC_BAD_ACCESS異常,我現在還不知道如何調試。任何幫助,將不勝感激。

這是實施。一切開始時我打了 「提交」 按鈕,觸發:

- (IBAction)submit:(id)sender {  
    [p_spinner startAnimating];  
    [self performSelector: @selector(sendData) 
      withObject: nil 
      afterDelay: 0]; 
} 

這裏是送出數據:

-(void)sendData{ 
    ... 
    _tempFilePath = ... ; 
    [[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil]; 
    [self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath]; 
    [p_spinner stopAnimating]; 
    //Pop back to previous VC 
    [self.navigationController popViewControllerAnimated:NO] ; 
} 

下面是setUpStreamsForInputFile稱爲上面:

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath { 
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath]; 
    [p_iStream setDelegate:self]; 
    [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
          forMode:NSDefaultRunLoopMode]; 
    [p_iStream open]; 
} 

最後,這是哪裏大多數邏輯發生:

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

    NSLog(@"stream %@ got event %x", stream, (unsigned)eventCode); 

    switch(eventCode) { 
     case NSStreamEventHasBytesAvailable: 
     { 
      if (stream == self.p_iStream){ 
       if(!_tempMutableData) { 
        _tempMutableData = [NSMutableData data]; 
       } 
       if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream 
        unsigned int buffer_len = 24000;//read in chunks of 24000 
        uint8_t buf[buffer_len]; 
        unsigned int len = 0; 
        len = [p_iStream read:buf maxLength:buffer_len]; 
        if(len) { 
         [_tempMutableData appendBytes:(const void *)buf length:len]; 
         NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData]; 
         _streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding]; //encode the data as Base64 string 
         [_tempFileHandle writeData:_streamdata];//write the data 
         [_tempFileHandle seekToEndOfFile];// and move to the end 
         _tempMutableData = [NSMutableData data]; //reset mutable data buffer 
         _streamdata = [[NSData alloc] init]; //release the data buffer 
        } 
       } 
      } 
      break; 
     case NSStreamEventEndEncountered: 
     { 
      [stream close]; 
      [stream removeFromRunLoop:[NSRunLoop currentRunLoop] 
           forMode:NSDefaultRunLoopMode]; 
      stream = nil; 
      //do some more stuff here... 
      ... 
      break; 
     } 
     case NSStreamEventHasSpaceAvailable: 
     case NSStreamEventOpenCompleted: 
     case NSStreamEventNone: 
     { 
      ... 
     } 
     } 
     case NSStreamEventErrorOccurred:{ 
      ... 
     } 
    } 
} 

注意:當我第一次發佈時,我的印象是錯誤的,這個問題與使用GCD有關。根據Rob的回答,我刪除了GCD代碼,問題依然存在。

回答

19

第一:在您的原始代碼中,您沒有使用後臺線程,而是主線程(dispatch_async但在主隊列中)。

當您計劃NSInputStream在默認runloop(所以,主線程的runloop)上運行時,主線程處於默認模式(NSDefaultRunLoopMode)時收到事件。但是:如果您檢查,默認的runloop在某些情況下(例如,在UIScrollView滾動和一些其他UI更新期間)更改模式。當主runloop處於與NSDefaultRunLoopMode不同的模式時,不會收到您的事件。

您的舊代碼與dispatch_async差不多,但移動了主線程上的UI更新。你必須只添加一些變化:

  • 調度的背景下,像這樣的東西:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 
dispatch_async(queue, ^{ 
    // your background code 

    //end of your code 

    [[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point 
}); 
  • 開始上線運行循環。這必須在調度異步調用的末尾(最後一行)來完成,與此代碼

[[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch 

嘗試,讓我知道

編輯 - 添加示例代碼:

更清楚,我複製粘貼您的原始代碼更新:

- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath { 
    self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath]; 
    [p_iStream setDelegate:self]; 

    // here: change the queue type and use a background queue (you can change priority) 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); 
    dispatch_async(queue,^{ 
     [p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
         forMode:NSDefaultRunLoopMode]; 
     [p_iStream open]; 

     // here: start the loop 
     [[NSRunLoop currentRunLoop] run]; 
     // note: all code below this line won't be executed, because the above method NEVER returns. 
    });  
} 

做此修改後,您:

​​

方法,將在同一線程上叫,你開始運行循環,後臺線程:如果你需要更新的用戶界面,它是你再次指派很重要到主線程。

額外的信息:

在我的代碼我使用dispatch_async在隨機背景隊列(其派遣的可用後臺線程的一個代碼,或者如果需要啓動一個新的,所有的「自動地」) 。如果你願意,你可以開始你自己的線程,而不是使用調度異步。此外,在發送「運行」消息之前,我不檢查runloop是否已經運行(但您可以使用currentMode方法檢查它,查看NSRunLoop參考以獲取更多信息)。它不應該是必須的,因爲每個線程只有一個關聯的NSRunLoop實例,因此發送另一個運行(如果已經運行)沒有任何不良:-)

您甚至可以避免直接使用runLoops並切換到完整的GCD方法,使用dispatch_source,但我從來沒有直接使用它,所以我現在不能給你一個「很好的示例代碼」

+0

謝謝,LombaX。只是爲了確保我明白你在說什麼。在我原來的問題中,我正在將dispatch_async(dispatch_get_global_queue(0,0)...)設置爲dispatch_async(dispatch_get_main_queue()...),然後當我設置流從文件中讀取時,我不知道我遵循的邏輯,所以我認爲我更好地確保...非常感謝! – PeterD 2013-03-11 19:07:49

+0

這是你的意思嗎? ' - (void)setUpStreamsForInputFile :(的NSString *)INPATH OUTPUTFILE:(的NSString *)outpath中{ \t self.p_iStream = ... \t [p_iStream setDelegate:自]; \t dispatch_async(dispatch_get_main_queue(),^ { \t \t [p_iStream scheduleInRunLoop: NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; \t \t [p_iStream open]; \t \t ** [[NSRunLoop currentRunLoop] run]; ** \t}); } – PeterD 2013-03-11 19:24:01

+0

並且這個: ' - (IBAction)提交:(id)發送者{p_spinner startAnimating]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0); dispatch_async(queue,^ {self-sendData]; ** [[NSRunLoop currentRunLoop] run]; **}); }' 對不起,我正在修改.. :) – PeterD 2013-03-11 19:24:19

4

NSStream需要運行循環。 GCD不提供一個。但是這裏你不需要GCD。 NSStream已經是異步的。只需在主線上使用它;這就是它的設計目的。

您還在後臺線程中進行多個UI交互。你不能那樣做。所有UI交互都必須在主線程上進行(如果刪除GCD代碼,這很容易)。

如果GCD有用,如果讀取和處理數據非常耗時,則可以在NSStreamEventHasBytesAvailable期間將該操作交給GCD。

+0

謝謝!我會這樣做,但實際上我之前沒有使用GCD就使用它。我添加了GCD,因爲我希望讀寫文件的所有任務都可以在後臺進行。相反,如果沒有GCD,我的旋轉工具就會動畫,日誌會顯示所有的流事件。正如你所說,我也認爲NSStream應該是異步發生的,但是我不確定我做錯了什麼。我可能會用我以前的版本更新我的問題,或者更好地問一個單獨的問題。謝謝回覆! – PeterD 2013-03-11 17:18:54

+0

因此,Rob,你是否建議在dispatch_async(dispatch_get_main_queue(),^ {...})中封裝NSStreamEventHasBytesAvailable中的邏輯; ?我不認爲編碼每個24000數據塊是非常耗時的,以證明GCD的開銷... – PeterD 2013-03-11 17:24:00

+0

我需要重新說明和更新我的問題。我刪除了GCD代碼,不僅我仍然看到相同的問題,而且我現在剛剛得到了EXC_BAD_ACCESS異常。一般情況下有些問題... – PeterD 2013-03-11 17:35:38

相關問題