9

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, ...;
@end

And the .m-file with the extension:

#import "SoundChainHelper.h"

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

@implementation SimpleAudioEngine(SoundChainHelper)

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

    va_list params;
    va_start(params,filename);

    while (filename) {
        [soundsInChain addObject:filename];
        filename = va_arg(params, NSString*);
    }
    va_end(params);
    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];
        currentSound++;
    }
}

@end

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.

gebirgsbärbel
  • 2,327
  • 1
  • 22
  • 38

2 Answers2

24

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;
@end

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);
}

@end

(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.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97
  • I forgot to state that I am working on iOS. I now added this information to the tags. On iOS this approach is barred. Do you know whether there are still ways to achieve this or do I need to create a new class that offers the functionality I want? – gebirgsbärbel May 08 '12 at 17:27
  • @gebirgsbaerbel: Barred on iOS? Where did you hear that? – Chuck May 08 '12 at 18:01
  • @Chuck in the documentation it says the feature is available on MacOS10.6 and newer. Also I tried it out and I basically get the error that the two methods are not known. The header file containing them does not seem to exist on iOS. Maybe I missed something? – gebirgsbärbel May 08 '12 at 18:05
  • 1
    You need to `#import ` in the file(s) where you use Objective-C runtime functions, including the associative reference functions. They are available on iOS. If you look at runtime.h, you can see that they're available in OS X 10.6 and higher, and iOS 3.1 and higher. – Andrew Madsen May 08 '12 at 18:51
  • Ah thank you, I was not able to find the proper import, even though it is at the top of your code... sometimes I am a little blind – gebirgsbärbel May 08 '12 at 20:30
  • I added that after my most recent comment, so you didn't miss anything :). Glad I could help! – Andrew Madsen May 08 '12 at 20:33
-1

You can not add iVars but can add property variables. Something like below:

In your .h:

#import <objc/runtime.h>

@interface Chair (Liking)

     @property (nonatomic, assign)BOOL liked;
 @end

In your .m:

#import "Chair+ChairCat.h"

@implementation Chair (Liking)

 -(BOOL)liked{

    return [ objc_getAssociatedObject( self, "_aliked" ) boolValue ] ;    
 }

-(void)setLiked:(BOOL)b{    
    objc_setAssociatedObject(self, "_aliked",  [ NSNumber numberWithBool:b ],OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;

 }

@end

Then somewhere in some other class say myViewController.m

#import "Chair+ChairCat.h"
- (void)viewDidLoad {
    /////
    Chair *chair = [[Chair alloc] init];
    [chair setLiked:NO];
}
Ajeet Sharma
  • 214
  • 2
  • 9