Don't let ARC mess with your callback

edited April 2013

I was just checking out The Engine (awesome library btw!) and noticed a potential stumbling block with ARC that I wanted to share.

If you have an ARC enabled Objective-C class that implements AEAudioPlayable, AEAudioChannel or related protocols, you need to use __unsafe_unretained liberally to keep ARC from interfering with your audio callback.

Here is an example callback implementation for the AEAudioPlayable protocol:

/* Not safe for use with ARC */
static OSStatus callback(id channel, AEAudioController *audioController, const AudioTimeStamp *time, UInt32 frames, AudioBufferList *audio)
{

    SinePlayable* THIS=(SinePlayable*) channel;

    float* bufs[2]={audio->mBuffers[0].mData,audio->mBuffers[1].mData};
    
    /* Basic sine generator. See vecLib for cooler stuff. */
    for(int i=0;i_phase);
        THIS->_phase=fmodf(THIS->_phase+THIS->_phase_inc, 2*M_PI);
    }
    return noErr;
}

If this is compiled using ARC, there will be retain and release calls inserted for the two ObjC arguments and the local THIS variable, since the default ownership is __strong.

This is bad. These calls are guarded by spin locks which can be preempted while locked. If (when) another thread gets preempted while holding that lock, the realtime audio thread will burn through a lot of cycles spinning in the lock rather than generating the groovy waveforms, and could overrun its deadline. The resulting glitching is what makes people cling to vinyl as a less versatile, but more predictable audio medium.

The safest solution is to disable ARC for that file by adding -fno-objc-arc to 'Compiler Flags' under Build Phases -> Compile Sources.

If you really really want to use ARC, you'll need to explicitly add __unsafe_unretained to every ObjC pointer:

static OSStatus callback(__unsafe_unretained id channel, __unsafe_unretained AEAudioController *audioController, const AudioTimeStamp *time, UInt32 frames, AudioBufferList *audio)
{
    __unsafe_unretained SinePlayable* THIS=(SinePlayable*) channel;

To catch sneaky ARC calls, you could set the following breakpoint in lldb, though it's very brute force and you won't get much in the way of audio or app responsiveness with it set :)

(lldb) br set -T AURemoteIO::IOThread -n objc_retain

Sign In or Register to comment.