views:

321

answers:

2

I'm rather surprised at how few objects implement NSCopying. This is the third time in two weeks where I have needed to duplicate an existing object, without having to either go to disk and reload, or recreate the object and set its settings.

Now I have to duplicate an AVAudioPlayer sound. Why? I want to play the sound twice, without having the sound.currentTime determine the playback location of the second playback.

I thought it might be easy to do a category or subclass AVAudioPlayer and implement copyWithZone. However, the internals of AVAudioPlayer are hidden and I can't easily copy them.

I am now yearning for the good old BlockMove(&var, &newVar, sizeof(varType));

How best to duplicate an AVAudioPlayer sound or a UIView view?

+3  A: 

The reason why many objects don't support NSCopying is because it's not always clear on what a copied object should be, and in particular, the excessively pedantic details of what it means to 'copy an object'.

I would take a pragmatic approach in this case. If you need an instance of AVAudioPlayer duplicated just once, I'd recommend this:

AVAudioPlayer *audioPlayer; // Assumed to be valid.
AVAudioPlayer *copiedPlayer = NULL;

copiedPlayer = [[[AVAudioPlayer allocate] initWithData:audioPlayer.data] autorelease];

This is not meant to be a general solution because it does not handle all cases. It's meant to be an example of how you can over come the problem while relying on some usage specific invariants. The most notable example in which this breaks is if audioPlayer was not initialized with a NSData object, but a url instead. It also makes certain relatively safe assumptions about the mutability of data, and how AVAudioPlayer was coded to handle those cases. (I did not look at the documentation long enough to see if such corner case behaviors are documented).

If this is something you need to do a lot, you can probably lash together a more complicated bit of code that implements NSCopying. Since a quick pass at the documentation turns up only two ways to init the object, via a NSData or via a url, and the instantiated object to be copied provides that information... then the implementation of a non-optimal deep copy NSCopying subclass is left as an exercise for the reader. :)

johne
This didn't work out as my AVAudioPlayer came from a file URL. Looking at some underlying Core Audio code samples, I see that Apple implemented a copy function a different way, which isn't really what I'm looking for either. See my "answer" since I am limited in space here in the comments section.
mahboudz
A: 

I saw an Apple sample file, CASound.mm, which has a -copyWithZone:

/* support NSCopying */
- (id)copyWithZone:(NSZone *)zone
{
    CASound* copy = NSCopyObject(self, 0, zone);

    copy->_impl = NULL;
    [copy initWithSound: self];
    return copy;
}

looking at initWithSound:

- (CASound*)initWithSound:(CASound *)other
{
    CASoundImpl* otherImpl = (CASoundImpl*)other->_impl;

    if (otherImpl->_url) {
     return [self initWithContentsOfURL: otherImpl->_url];
    } else if (otherImpl->_data) {
     return [self initWithData: otherImpl->_data];
    } else {
     return NULL;
    }
}

Now, I don't know if Apple likes us messing around with the private fields like _impl, but this basically boils down to the same thing as getting the AVPlayer all over again from the file, and not just making an in-memory copy of the existing sound.

I was actually hoping that maybe NSCopyObject would do the deed, but I see Apple doesn't rely on it for the entire copy.

mahboudz