iosmultithreadinguiviewcontrollerexc-bad-accessnsblockoperation

Asynchronously loading sound resources in viewDidLoad crashes


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


Solution

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