2016-02-08 58 views
1

這裏是上下文: 我一直在開發音頻相關的應用程序一段時間了,我有點碰壁,不知道下一步該怎麼做。iOS:在回調函數中調用setNeedDisplay時的線程問題

我最近在應用中實現了一個繪製音頻輸出的FFT顯示的自定義類。這個類是UIView的一個子類,這意味着每次我需要繪製一個新的FFT更新時,我需要用新的樣本值在我的類實例上調用setNeedDisplay。因爲我需要爲每一幀繪製一個新的FFT(幀〜1024樣本),這意味着我的FFT的顯示函數被稱爲很多(1024/SampleRate = 0.02321秒)。至於樣本計算,它完成了44'100 /秒。我在iOS中管理線程並不是很有經驗,所以我讀了一點關於它的內容,這裏是我如何做到的。

它是如何完成的:我有一個NSObject「AudioEngine.h」的子類,它負責處理我應用程序中的所有DSP處理,這是我設置FFT顯示的地方。所有樣本值都計算並分配給dispatch_get_global_queue塊內的我的FFT子類,因爲這些值需要在後臺不斷更新。一旦樣本指標已經達到了最大幀數的setneedDisplay方法被調用,這是一個dispatch_async(dispatch_get_main_queue)

內完成。在「AudioEngine.m」

for (k = 0; k < nchnls; k++) { 

      buffer = (SInt32 *) ioData->mBuffers[k].mData; 

      if (cdata->shouldMute == false) { 

       buffer[frame] = (SInt32) lrintf(spout[nsmps++]*coef) ; 

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
         @autoreleasepool { 

          // FFT display init here as a singleton 
          SpectralView *specView = [SpectralView sharedInstance]; 

          //Here is created a pointer to the "samples" property of my subclass 
          Float32 *specSamps = [specView samples]; 

          //set the number of frames the FFT should take 
          [specView setInNumberFrames:inNumberFrames]; 

          //scaling sample values 
          specSamps[frame] = (buffer[frame] * (1./coef) * 0.5); 
         } 
        }); 

      } else { 
      // If output is muted 
       buffer[frame] = 0; 
      } 
     } 

     //once the number of samples has reached ksmps (vector size) we update the FFT 
     if (nsmps == ksmps*nchnls){ 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        SpectralView *specView = [SpectralView sharedInstance]; 
        [specView prepareToDraw]; 
        [specView setNeedsDisplay]; 
       }); 

什麼我的問題是:

  1. 我得到各種線程問題,特別是在主線程上,例如Thread 1: EXC_BAD_ACCESS (code=1, address=0xf00000c),有時在應用程序啓動時會調用viewDidLoad,而且每當我嘗試與w進行交互時ith任何UI對象。
  2. 即使在FFT顯示屏上,UI響應速度也變得異常緩慢。

我認爲這個問題是:這肯定是關係到一個線程問題,因爲你可能知道,但我對這個話題確實沒有經驗。我想可能會強制主線程上的任何UI顯示更新,以解決我所遇到的問題,但又一次;我甚至不知道如何正確地做到這一點。

任何輸入/洞察將是一個巨大的幫助。 在此先感謝!

+1

什麼的線程上面的代碼運行嗎? _我想可能會強制主線程上的任何UI顯示更新,以解決我的問題,但又一次;我甚至不知道如何正確地做到這一點。所有的UIKit調用都必須在主線程中進行,並且您可以通過調用帶有主隊列的'dispatch_async()'從另一個線程執行此操作,就像您在上面的最後一行代碼。你不應該從'dispatch_get_global_queue()'隊列中調用UIKit調用(這是爲了併發執行)。 – Mark

+0

你確定你的'SpectralView'單例是線程安全的嗎? 'sharedInstance'方法應該使用'dispatch_once',並確保你從後臺調用的方法不會以任何方式更新UI。您還應該確保從多個線程調用的任何屬性都能正確序列化。 – Hamish

+0

@ originaluser2真的很好點。我甚至沒有注意到我沒有在我的視圖中使用'dispatch_once'。 –

回答

2

正如您所寫,您的SpectralView*需要完全線程安全。

您的for()循環首先將幀/樣本處理關閉到高優先級併發隊列。由於這是異步的,它將立即返回,此時您的代碼將對主要威脅列入請求以更新頻譜視圖的顯示。

這幾乎可以保證光譜視圖將不得不更新顯示,同時後臺處理代碼也更新光譜視圖的狀態。

還有第二個問題;您的代碼將最終並行處理所有通道。一般而言,未發佈的併發性是性能下降的一個原因。此外,無論該通道的處理是否完成,您都將針對每個通道的主線程進行更新。


該代碼需要重新構造。你真的應該從視圖層拆分模型層。模型圖層可以被編寫爲線程安全的,在處理過程中,您可以抓取要顯示的數據的快照並在SpectralView上折騰。或者,您的模型圖層可能會有一個標記,SpectralView可以鍵入該標記以知道它不應該讀取數據。

這是相關的:

https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

+1

問題的好摘要。對OP來說:一個好的經驗法則:如果你有一個對象是UIView的子類,它不應該從後臺線程觸及。 –

+0

非常感謝這個答案和真正有用的資源。好像我現在需要做一些閱讀! –