Reputation: 1102
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
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
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
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
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
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
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