2016-02-08 59 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

非常感谢这个答案和真正有用的资源。好像我现在需要做一些阅读! –