Cocos2D Webcam Viewer, Part 2: Asynchronous Texture Loading

On February 23, 2012, in idevblogaday, by Steffen Itterheim

I updated the Cocos2D Webcam Viewer project from a previous article to download a file from the web asynchronously, and then load its texture asynchronously as well. You can now switch between the two modes to see how asynchronous operations almost completely removed the pauses the app experiences in synchronous mode. Just tap the screen to switch modes.

To visualize the lag I added a constantly moving sprite at the bottom. This makes the lag easier to spot than a framerate counter. I also removed all error checking code from this article to make the code easier to read. As always you can find the Cocos2D Webcam Viewer source code with full error checking on the LearnCocos2D github repository.

Downloading a file from the web synchronously

Downloading a file from the web turns out to be super-simple. You only need three lines of code, and of course the address and path to the file on the server:

Downloading a file from the web asynchronously

Unfortunately, NSData has no convenience method that allows you to load data asynchronously. Fortunately, NSObject has the method that you need. It is called performSelectorInBackground:withObject:. You don’t need to know a single thing about threading to utilize multithreading.

But then again performSelectorInBackground only takes a single argument but we must pass at least the url and the file to download to. This is a common situation that you’ll be faced with from time to time. There is a simple solution: create a new class that holds all of the data you need.

I solved this problem by creating the AsyncFileDownloadData class which is used to pass multiple arguments to a method which only takes a single object argument.

In Objective-C, this is generally recommended and preferred over creating C structs. Mainly because you can add an object into any collection (NSArray, NSDictionary, etc) whereas you can’t do that with a C struct.

Whenever you think you need a C struct, create an object instead. If necessary, you can make the ivars @public to make the object behave like a C struct (access @public ivars with: object->url). This gives you the same performance as a C struct and only slightly higher memory usage.

Perform selector in background

Back to asynchronous downloading: you can now rewrite the initial code to perform a selector on a background thread like this:

In case you’re wondering: performSelectorInBackground retains the afd object, so it is safe to autorelease it. Of course if you’re using ARC (which you really should use in every new project – but more on that some other time) this wouldn’t concern you at all.

The downloadInBackground: method takes the AsyncFileDownloadData object as argument and then performs the exact same code as you’ve seen earlier. Except that this method is now running on a separate, background thread.

Perform selector on main thread

Knowing that the downloadInBackground: selector is running on a background thread is crucial information because some operations have to be done on the main thread, and loading OpenGL textures is one of these operations.

That’s why the updateSprites: selector is performed on the main thread via performSelectorOnMainThread:withObject:waitUntilDone:. It also receives the afd object because it too needs the localFile and spriteTag vars to do its job. Again, no extra retain is necessary.

The only job of updateSprites: is to extract the information from the afd object and call the already existing method updateTexturesFromFile:forSpritesWithTag:. It just relays the call:

Your experience may vary. You may be able to perform other selectors on the background thread, too. It may or may not crash. But even if it works, you don’t know what kind of side effects this may cause because you could have the same method running on the main thread and the background thread simultaneously.

Therefore it is good practice to “return the call” to the main thread after the background task is completed.

Loading Textures asynchronously with Cocos2D

Loading a texture in Cocos2D is done via the CCTextureCache class. Here’s the code that loads our newly downloaded texture file synchronously, and it calls updateChildrenWithTag:texture: right away:

Now if you want to load the texture asynchronously, all you need is to use the CCTextureCache method addImageAsync:target:selector: and be done with it.

But wait, it’s not that simple in this case. The addImageAsync method doesn’t provide you with a way to pass an extra object along, so once the texture has been loaded and the callback selector runs, you don’t have the spriteTag available that the updateChildrenWithTag: method requires!

The (seemingly) easiest solution is to bite the bullet and call different selectors depending on the sprite tag: