【问题标题】:iOS: Multi-Threading issues when loading sounds in the backgroundiOS:在后台加载声音时的多线程问题
【发布时间】:2012-08-13 09:08:01
【问题描述】:

我继承了一些需要整理的代码。该应用程序有一些核心声音,目前,有很多 AVAudioPlayer 实例附加到各种 ViewController 都播放相同的几个声音。

作为重构练习的一部分,我决定实现一个名为SoundController 的单例类。这个类将为每个需要播放的声音包含一个 AVAudioPlayer,而不是每个 ViewController 实例化自己的,他们可以轻松地只使用一个:

[[SoundController controller].majorFunctionSound playAtTime:0];

另一件重要的事情是确保在所有声音被使用之前都已调用 prepareToPlay: 以最大程度地减少任何延迟。由于只有少数声音并且它们都可能在任何用户会话期间使用,因此在 SoundController 首次实例化时预加载所有声音(在后台线程上)是有意义的。在 init 方法中我有这样的东西:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, nil, ^(void)          
{
NSURL *soundURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/MAJOR FUNCTION.m4a", [[NSBundle mainBundle] resourcePath]]];
_majorAudioSound = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:nil];
[_majorAudioSound  prepareToPlay];
});

majorAudioSound 是 (readonly, strong)@synthesize majorAudioSound = _majorAudioSound.

我担心的是,这在并发方面的工作效果有多差(或好不好),以及我可以做些什么来改进代码。具体来说,如果我这样做:

[[SoundController controller].majorFunctionSound playAtTime:0];

很明显,majorFunctionSound 有可能无法正确启动,具体取决于后台初始化块是否已完成。如果属性返回 nil 并且声音根本无法播放,是否会发生更糟糕的情况?

可能还有什么其他问题?有没有办法始终确保 AVAudioPlayer 已正确设置?

【问题讨论】:

    标签: objective-c ios concurrency avaudioplayer objective-c-blocks


    【解决方案1】:

    首先,我想让你重新考虑一下你的类是否必须是单例,因为你打算只拥有它的一个实例。这当然是一种方法,但我认为您的初始化问题继承自您决定使用单例类这一事实。

    假设您有一个名为 SoundManager 的类,并且您已将其设为 Singleton 类。

    当您在应用程序的任何位置请求 SoundManager 的实例时,您会希望假定返回的实例已准备好立即使用。如果您在 SoundManager 中有一个异步的 init 方法,那么您确实有一个设计问题,因为您永远不必知道何时请求 Singleton 是否是第一次初始化。

    由于 SoundManager 需要初始化,我会让我的应用在某种处理应用程序流的基类中拥有 SoundManager 的实例,而不是使其成为单例。要么你可以让你的 AppDelegate 实例化唯一的 SoundManager,要么你可以有一个名为 ApplicationController 的类,或者你可以在应用程序初始化时加载应用程序所需的所有内容。然后,您可以通过此控制器类通过传递引用或让您的 ApplicationController 成为单例来访问 SoundManager 实例。当然,如果 SoundManager 是单例的,这也可以,只要您确保在启动时对其进行初始化,但我更喜欢尽可能少的单例类。

    现在是关于知道您的声音是否已加载的问题。

    我建议您在让用户开始使用应用程序之前加载所有声音。同时,你可以向用户展示一些东西,比如加载屏幕、进度条,如果你愿意的话还可以播放声音/音乐。下面是一个结构示例:

    • 创建一个名为 SoundManager 的类,其属性“已加载”从一开始就为假
    • 创建一个名为 ApplicationController 的类来实例化 SoundManager,以及您可能拥有的其他有用的类,如 TextureManager 或 LocationManager 等。
    • 当应用启动时,实例化 ApplicationController,然后实例化 SoundManager。
    • 显示加载屏幕
    • 让 SoundManager 先加载“加载声音”,加载后开始播放
    • 声音加载完成后,将“loaded”属性设置为true
    • ApplicationController 加载完所有内容后,通过淡出加载屏幕让用户开始使用应用程序。

    如果您需要用户在加载声音之前就开始使用应用程序,那么您仍然可以通过使用名为“已加载”的属性来使用相同的方法。请记住保持对该属性的处理同步。

    【讨论】:

    • 我有点期待这一点,因为这将是我通常在其他语言中采用的方法(用于 IOC 等)。我会尝试并尝试实施您的建议 - 我正在寻找的是使视图控制器中与声音有关的代码量最少。另外,请您更详细地解释一下您的最后一句话吗?
    • “记住要保持对这个属性的处理同步” - 意味着如果你正在从多个线程访问一个资源,你需要确保这个线程不被读写来保证它的安全。同时。您可以使用 @synchronized-blocks 或使用原子属性(不是非原子属性)并始终从其 getter/setter 方法访问它们。
    • 通过尝试将代码从视图控制器类中排除,您绝对走在正确的轨道上。将它们保持最小可以为您省去很多麻烦,并使您的应用程序更易于维护、扩展和重用。继续加油!
    • 好的,我已经创建了 ApplicationController 作为一个单例它 init 的 SoundManager。 VC 可以通过 ApplicationController 访问 SoundManager。但是,我仍然需要确保 ApplicationController 及其所有关联的管理器在任何 VC 尝试使用它之前都已完全初始化。所以我回到了你最初提出的相同情况 - 所以基本上将 init 推送到后台线程没有什么意义,因为它都需要在应用程序继续运行之前加载,对吧?
    • 我想如果有多个 Manager 文件,那么我可以同时启动它们的初始化操作,但我仍然需要等待所有内容都加载完毕,然后应用程序委托才能将控制权传递给 RootVC
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-12
    • 1970-01-01
    相关资源
    最近更新 更多