Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A subclass of AVAudioPlayer that retains itself until playback ends or encounters an error, so you don't have to keep a strong reference to it. Supports pass-through to an exogenous delegate that gets set (which is captured in a zeroing weak reference). It's probably inadvisable to use this, but what the heck!
#import <AVFoundation/AVFoundation.h>
@interface OneShotAVAudioPlayer : AVAudioPlayer
@end
#import "OneShotAVAudioPlayer.h"
#import <AVFoundation/AVFoundation.h>
#import <objc/runtime.h>
@interface OneShotAVAudioPlayer () <AVAudioPlayerDelegate>
@property(weak) id<AVAudioPlayerDelegate> p_exogenousDelegate;
@end
@implementation OneShotAVAudioPlayer
static void * const OneShotAVAudioPlayerKey = (void*)&OneShotAVAudioPlayerKey;
- (id)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError
{
if (self = [super initWithContentsOfURL:url error:outError])
{
// Retain our
objc_setAssociatedObject(self, OneShotAVAudioPlayerKey, self, OBJC_ASSOCIATION_RETAIN);
[super setDelegate: self];
}
return self;
}
- (id)initWithData:(NSData *)data error:(NSError **)outError;
{
if (self = [super initWithData:data error:outError])
{
objc_setAssociatedObject(self, OneShotAVAudioPlayerKey, self, OBJC_ASSOCIATION_RETAIN);
[super setDelegate: self];
}
return self;
}
- (void)setDelegate:(id<AVAudioPlayerDelegate>)delegate
{
self.p_exogenousDelegate = delegate;
}
- (id<AVAudioPlayerDelegate>)delegate
{
return self.p_exogenousDelegate;
}
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
@try
{
if ([self.p_exogenousDelegate respondsToSelector: _cmd])
[self.p_exogenousDelegate audioPlayerDidFinishPlaying:player successfully:flag];
}
@finally
{
// Make a strong ref so we stay alive through the scope of this function
typeof(self) keepAlive = self;
// Give up the self retain
objc_setAssociatedObject(keepAlive, OneShotAVAudioPlayerKey, nil, OBJC_ASSOCIATION_RETAIN);
// Push in the "real" (outside) delegate, cause our job is done here.
[super setDelegate: self.p_exogenousDelegate];
}
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
@try
{
if ([self.p_exogenousDelegate respondsToSelector: _cmd])
[self.p_exogenousDelegate audioPlayerDecodeErrorDidOccur:player error:error];
}
@finally
{
// Make a strong ref so we stay alive through the scope of this function
typeof(self) keepAlive = self;
// Give up the self retain
objc_setAssociatedObject(keepAlive, OneShotAVAudioPlayerKey, nil, OBJC_ASSOCIATION_RETAIN);
// Push in the "real" (outside) delegate, cause our job is done here.
[super setDelegate: self.p_exogenousDelegate];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL retVal = [super respondsToSelector: aSelector];
if (!retVal)
{
struct objc_method_description method = protocol_getMethodDescription(@protocol(AVAudioPlayerDelegate), aSelector, YES, YES);
if (method.name)
{
retVal = [self.p_exogenousDelegate respondsToSelector: aSelector];
}
}
return retVal;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
id retVal = [super forwardingTargetForSelector:aSelector];
if (!retVal)
{
struct objc_method_description method = protocol_getMethodDescription(@protocol(AVAudioPlayerDelegate), aSelector, YES, YES);
if (method.name && [self.p_exogenousDelegate respondsToSelector: aSelector])
{
retVal = self.p_exogenousDelegate;
}
}
return retVal;
}
- (void)setNumberOfLoops:(NSInteger)numberOfLoops
{
if (numberOfLoops < 0)
{
NSLog(@"Warning! You have set an infinite loop count for an instance of OneShotAVAudioPlayer. This means the instance will effectively be leaked.");
}
[super setNumberOfLoops: numberOfLoops];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment