ultramiraculous
ultramiraculous

Reputation: 1102

Using objc_setAssociatedObject with weak references

I know that OBJC_ASSOCIATION_ASSIGN exists, but does it zero the reference if the target object is dealloced? Or is it like the old days where that reference needs to get nil-ed or we risk a bad access later on?

Upvotes: 27

Views: 7922

Answers (6)

Nuno Vieira
Nuno Vieira

Reputation: 303

In case people still need a workaround for this, here is a solution in Swift to replace the normal objc_getAssociatedObject with a custom one to handle weak objects:

func objc_getAssociatedWeakObject(_ object: AnyObject, _ key: UnsafeRawPointer) -> AnyObject? {
    let block: (() -> AnyObject?)? = objc_getAssociatedObject(object, key) as? (() -> AnyObject?)
    return block != nil ? block?() : nil
}

func objc_setAssociatedWeakObject(_ object: AnyObject, _ key: UnsafeRawPointer, _ value: AnyObject?) {
    weak var weakValue = value
    let block: (() -> AnyObject?)? = {
        return weakValue
    }
    objc_setAssociatedObject(object, key, block, .OBJC_ASSOCIATION_COPY)
}

Upvotes: 0

Stream
Stream

Reputation: 9493

One more option similar to WeakObjectContainer:

- (id)weakObject {
    id (^block)(void) = objc_getAssociatedObject(self, @selector(weakObject));
    return (block ? block() : nil);
}

- (void)setWeakObject:(id)object {
    id __weak weakObject = object;
    id (^block)(void) = ^{ return weakObject; };
    objc_setAssociatedObject(self, @selector(weakObject),
                             block, OBJC_ASSOCIATION_COPY);
}

Upvotes: 13

Oskar
Oskar

Reputation: 3702

Swift version of answer by 0xced (with type support):

public class WeakObjectContainer<T: AnyObject>: NSObject {

    private weak var _object: T?

    public var object: T? {
        return _object
    }

    public init(with object: T?) {
        _object = object
    }

}

Upvotes: 1

0xced
0xced

Reputation: 26508

As ultramiraculous demonstrated, OBJC_ASSOCIATION_ASSIGN does not do zeroing weak reference and you risk to access a deallocated object. But it’s quite easy to implement yourself. You just need a simple class to wrap an object with a weak reference:

@interface WeakObjectContainer : NSObject
@property (nonatomic, readonly, weak) id object;
@end

@implementation WeakObjectContainer
- (instancetype) initWithObject:(id)object
{
    if (!(self = [super init]))
        return nil;

    _object = object;

    return self;
}
@end

Then you must associate the WeakObjectContainer as OBJC_ASSOCIATION_RETAIN(_NONATOMIC):

objc_setAssociatedObject(self, &MyKey, [[WeakObjectContainer alloc] initWithObject:object], OBJC_ASSOCIATION_RETAIN_NONATOMIC);

and use the object property to access it in order to get a zeroing weak reference:

id object = [objc_getAssociatedObject(self, &MyKey) object];

Upvotes: 35

ultramiraculous
ultramiraculous

Reputation: 1102

After trying it out, the answer is NO.

I ran the following code under the iOS 6 Simulator, but it would probably have the same behavior with previous iterations of the runtime:

NSObject *test1 = [NSObject new];

NSObject __weak *test2 = test1;

objc_setAssociatedObject(self, "test", test1, OBJC_ASSOCIATION_ASSIGN);

test1 = nil;

id test3 = objc_getAssociatedObject(self, "test");

In the end, test1 and test2 are nil, and test3 is the pointer previously stored into test1. Using test3 would result in trying to access an object that had already been dealloced.

Upvotes: 4

ipmcc
ipmcc

Reputation: 29886

This behavior isn't specified in the docs or headers as best I can tell, so it's likely an implementation detail that you shouldn't count on, even if you were able to discern what the current behavior is. I would guess that it is not zeroed out. Here's why:

In general, there is no need to nil out references in iVars during -dealloc. If an object is dealloced, it shouldn't matter if its iVars were zeroed out, because any further accessing of the dealloced object or its iVars is, in and of itself, a programming error. In fact, I've heard some argue that it's better to not clear out references during -dealloc, because it will make erroneous accesses more obvious/expose bugs sooner.

EDIT: Oh, I guess I misread your question. You want "zeroing weak references". Associated storage doesn't appear to support those. You could make a trivial pass-through class with one ivar/property marked as __weak and achieve the same effect that way. A little kludgey, but it'd work.

Upvotes: 0

Related Questions