gebirgsbärbel gebirgsbärbel - 3 years ago 213
Objective-C Question

Objective-C Category and new iVar

I try to extend the functionality of SimpleAudioEngine of cocos2d with the ability to play several sound effect one after another as some kind of chain. I tried to do this with an extension. However I now realized that I probably also need an iVar to remember the names of all sound files and one to remember which sound is currently playing.

However it seems that I cannot add iVars in a category. Instead I tried to use an extension, but it seems that they need to be in the original .m file of the class so that also would not work. Is there yet another way, that allows me to do this?

The header with the category

#import <Foundation/Foundation.h>
@interface SimpleAudioEngine(SoundChainHelper)<CDLongAudioSourceDelegate>
-(void)playSoundChainWithFileNames:(NSString*) filename, ...;

And the .m-file with the extension:

#import "SoundChainHelper.h"

@interface SimpleAudioEngine() {
NSMutableArray* soundsInChain;
int currentSound;

@implementation SimpleAudioEngine(SoundChainHelper)

// read in all filenames and start off playing process
-(void)playSoundChainWithFileNames:(NSString*) filename, ... {
soundsInChain = [[NSMutableArray alloc] initWithCapacity:5];

va_list params;

while (filename) {
[soundsInChain addObject:filename];
filename = va_arg(params, NSString*);
currentSound = 0;
[self cdAudioSourceDidFinishPlaying:nil];

// play first file, this will also always automatically be called as soon as the previous sound has finished playing
-(void)cdAudioSourceDidFinishPlaying:(CDLongAudioSource *)audioSource {
if ([soundsInChain count] > currentSound) {
CDLongAudioSource* mySound = [[CDAudioManager sharedManager] audioSourceForChannel:kASC_Right];
[mySound load:[soundsInChain objectAtIndex:0]];
mySound.delegate = self;
[mySound play];


Alternatively I tried to define the iVars as properties, which will compile. However I can neither synthesize them nor do I have any other possibility to bind them to any method.

I try to implement the functionality as a category of SimpleAudioEngine so that I only need to remember one class that deals with all my sound issues. and so that I can create a chain as simple as this:

[[SimpleAudioEngine sharedEngine] playSoundChainWithFileNames:@"6a_loose1D.mp3", @"6a_loose2D.mp3", @"6a_loose3D.mp3", @"6a_loose4D.mp3", @"6b_won1D.mp3", nil];

If there is another way that yields the same/ a similar result I would also be very thankful.

Answer Source

You are correct that you can't add instance variables (or synthesized @properties) to a category. You can workaround this limitation using the Objective-C runtime's support for Associative References

Something like this:

In your .h:

@interface SimpleAudioEngine (SoundChainHelper)
    @property (nonatomic, retain) NSMutableArray *soundsInChain;

In your .m:

#import <objc/runtime.h>
static char soundsInChainKey;

@implementation SimpleAudioEngine (SoundChainHelper)

- (NSMutableArray *)soundsInChain
   return objc_getAssociatedObject(self, &soundsInChainKey);

- (void)setSoundsInChain:(NSMutableArray *)array
    objc_setAssociatedObject(self, &soundsInChainKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);


(The standard disclaimer applies. I typed this in the browser, and didn't test it, but I have used this technique before.)

The documentation I linked to has a lot more information about how associative references work.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download