All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:@"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper
(called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:@selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init
of FHSCoachMarkView
looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:@"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation
and dispatch_async
and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by @Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle
? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks, Scott
I figured this out: I had set up a listener on the SoundHelper
object (NSUserDefaultsDidChangeNotification
) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView
was also making changes to NSUserDefaults
. In the SoundHelper
, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache
instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.