Search code examples
iosobjective-cxcodeobjective-c++

How to avoid EXC_BAD_ACCESS error when indirectly accessing weak ivar from inside dealloc


I have an Objective-C layer that wraps a C++ component. The current design is meant to be robust, meaning that the user may set the instance to nil anytime (ARC) and the underlying component will clean itself up properly, and synchronously.

The problem I have is that the Obj-C instance on top passes itself to the underlying C++ layer as a __weak reference to be accessed during operation (e.g. calling delegate, changing some states, etc.) and when a user deallocs the Obj-C instance by setting it to nil, an EXC_BAD_ACCESS occurs when trying to access it from inside dealloc.

Here's some sample code solely to demonstrate the problematic scenario.

Wrapper.h

#import <Foundation/Foundation.h>
#import "Underlying.h"

@interface Wrapper : NSObject {
    Underlying* _cppUnderlying;
}

- (instancetype)init;
- (void)dealloc;

@end

Wrapper+Private.h

#import "Wrapper.h"

@interface Wrapper () {
@package
    NSMutableDictionary* _dict;
}

@end

Wrapper.mm

#import "Wrapper+Private.h"

@implementation Wrapper

- (instancetype)init
{
    self = [super init];
    _cppUnderlying = new Underlying(self);
    _dict = [NSMutableDictionary dictionary];
    return self;
}

- (void)dealloc
{
    delete _cppUnderlying;
}

@end

Underlying.h

@class Wrapper;

class Underlying
{
public:
    Underlying(Wrapper* wrapper);
    ~Underlying();

private:
    __weak Wrapper* _wrapper;
};

Underlying.mm

#include "Underlying.h"
#import "Wrapper+Private.h"

Underlying::Underlying(Wrapper* wrapper) :
    _wrapper(wrapper)
{
}

Underlying::~Underlying()
{
    // ERROR OCCURS HERE.
    [_wrapper->_dict setValue:@"value1" forKey:@"key1"];
}

Initially, the question was "Why the error?!" but then, I just found out about this: Weak property is set to nil in dealloc but property's ivar is not nil which includes a detailed explanation (basically, objc_loadWeak() returns nil as soon as dealloc starts).

Now, the question would be: What kind of Obj-C design practice could I adopt to avoid this situation altogether? The C++ layer takes care of all session cleanup (if any ongoing) synchronously in its destructor. It seems like the same cannot be done in Objective-C. Should I provide a 'release' or 'close' method to asynchronously do all the cleanup before the user is ALLOWED to dealloc the instance?

Thanks!


Solution

  • How about inside -[Wrapper dealloc], before you do delete _cppUnderlying;, you first call something like _cppUnderlying->cleanup(_dict); where you explicitly pass your dictionary, or even _cppUnderlying->cleanup(self); where you pass the entire object, and let the Underlying take care of all the cleanup there?