iOS: Multi-Threading issues when loading sounds in the background

I have inherited some code that is in need of a bit of a tidy up. The app has a handful of core sounds and currently, there are lots of AVAudioPlayer instances attached to various ViewController's all playing the same few sounds.

As part of the refactoring exercise, I have decided to implement a singleton class called SoundController. This class will contain one AVAudioPlayer for each sound that needs playing, and instead of each ViewController instantiating their own, they can easily make use of just the one:

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

Another important thing is to ensure that prepareToPlay: has been called on all the sounds prior to them being used to minimise any delays. Since there are only a handful of sounds and they are all likely to be used during any user session, it makes sense to preload all the sounds (on a background thread) when SoundController is first instantiated. In the init method I have something like this:

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 is (readonly, strong). @synthesize majorAudioSound = _majorAudioSound.

My concerns are around how poorly (or well) this is going to work in terms of concurrency and what I can do to improve the code. Specifically, if I do this:

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

There is clearly a chance that majorFunctionSound wont be initiated properly depending on whether the background initialisation block has completed yet. Is the worse that could happen that the property returns nil and the sound simply doesn't play?

What other issues might there be? Is there a way to always ensure that the AVAudioPlayer has been properly set up?

Answers


First of all, I want you to think a second thought on whether your class have to be a singleton just because you intend to only have one instance of it. It is of course one way to do it, but I think that your initialization issues inherits from the fact that you decided to use a singleton class.

Lets say you have a class called SoundManager and you have made it a Singleton class.

When you ask for the instance of the SoundManager anywhere in your app, you will want to assume that the returned instance is ready to be used immediately. If you inside your SoundManager have an init method that is asynchronous, then you do have a design issue, since you should never have to know when you ask for a Singleton if it is the first time or not it has been initialized.

Since the SoundManager requires initialization I would let my app have an instance of the SoundManager in some kind of base class that takes care of the applications flow, instead of making it a Singleton. Either you could just let your AppDelegate instantiate the one and only SoundManager, or you could have a class called ApplicationController or something where you load all the stuff you need for the app when it initializes. Then you can reach the SoundManager instance via this controller class by passing a reference or by letting your ApplicationController be a singleton. Of course this also works if SoundManager is singleton, as long as you make sure you initialize it on startup, but I prefer to have as few singleton classes as possible.

Now to your question about knowing if your sounds are loaded or not.

I would recommend that you load all sounds before you let the user start using the app. In the meanwhile you could show something to the user, like a loading screen, a progressbar, and play sounds/music if you wish. Here is an example of a structure:

  • Create a class called SoundManager with a property "loaded" that is false from start
  • Create a class called ApplicationController that instantiate the SoundManager, and other useful classes you might have, like TextureManager or LocationManager etc.
  • When the app starts, instantiate ApplicationController, which in turn instantiates SoundManager.
  • Show a loading screen
  • Let SoundManager load the "loading sound" first and once it is loaded, start playing it
  • When the loading of sounds is completed, set the "loaded" property to true
  • When ApplicationController has loaded everything, let the user start using the app by fading out the loading screen.

If you need the user to start using the app even before the sounds are loaded, then you can still use the same approach by having a property called "loaded". Remember to keep the handling of this property synchronized.


Need Your Help

PouchDB: What to expect on a remote url of the database?

html5 pouchdb

I am trying to create an offline first mobile app using PouchDB as a database.

How do I build the database connection string if the website is on the same server as the db?

c# asp.net database database-connection connection-string

I have a problem with my connection string and I couldn't find anything on the web that could help me. So I have a test website on which I connect to a database which is not on the same server. The...