使用objc_setAssociatedObject实现弱引用

25
我知道OBJC_ASSOCIATION_ASSIGN存在,但如果目标对象被dealloced,它会将引用清零吗?还是像过去一样,需要将该引用赋值为nil,否则我们将面临错误访问的风险?

是的,没错。我会试一下的。 - ultramiraculous
6个回答

34

如超级神奇所示,OBJC_ASSOCIATION_ASSIGN不能进行零化弱引用,您可能会访问一个已释放的对象。但是实现起来非常简单。您只需要使用一个简单的类来包装带有弱引用的对象:

@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

然后您必须将WeakObjectContainer与OBJC_ASSOCIATION_RETAIN(_NONATOMIC)关联:

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

同时使用object属性来访问它,以便获取一个零化弱引用:

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

为什么不直接使用NSValue::valueWithNonretainedObject:呢? - AlexDenisov
2
因为 -[NSValue nonretainedObjectValue] 不返回 weak 对象,因此不执行零弱引用。 - 0xced
很酷,但有一个小问题:WeakObjectContainer本身的对象仍然被保留,意味着泄漏了。目标对象很可能已经被收集并且引用被清零,但是包装器仍然存在。 - n13
2
当它们的所有者被释放时,相关对象会自动释放。 - 0xced

13

还有一种类似于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);
}

很好的解决方案,省去了自己创建包装类的麻烦。谢谢! - JRG-Developer
该代码块应将其返回值标注为_Nullable - Kentzo

4

尝试后,答案是否定的。

我在iOS 6模拟器下运行了以下代码,但是在之前的运行时版本中可能会有相同的行为:

NSObject *test1 = [NSObject new];

NSObject __weak *test2 = test1;

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

test1 = nil;

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

最终,test1和test2都是空值,而test3是之前存储在test1中的指针。使用test3会导致尝试访问已经被释放的对象。

那里有错别字吗?“test1”与“test”和“test2”有什么关系? - Jeff

1
Swift版本的答案0xced(带类型支持):
public class WeakObjectContainer<T: AnyObject>: NSObject {

    private weak var _object: T?

    public var object: T? {
        return _object
    }

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

}

0
据我所知,文档或头文件中没有指定这种行为,因此它很可能是一个实现细节,即使您能够确定当前的行为,也不应该依赖它。我猜它不会被清零。原因如下:
通常,在dealloc期间不需要将iVars引用设置为nil。如果对象被释放,那么它的iVars是否被清零都无关紧要,因为任何进一步访问已释放的对象或其iVars本身就是编程错误。事实上,我听说有人认为在dealloc期间最好不要清除引用,因为这样会使错误访问更加明显/更早地暴露出错误。
编辑:哦,我想我误解了你的问题。你想要“零弱引用”。相关存储似乎不支持这些。您可以创建一个带有一个标记为__weak的ivar/property的微不足道的传递类,并以这种方式实现相同的效果。有点笨拙,但它会起作用。

我的意思是,在我所看的情况下,如果我在dealloc后尝试访问对象,这并不是一个错误。如果我有一个弱引用,比如对代理的引用,我预计在我发送消息时它可能已经不存在了。 - ultramiraculous

0

如果人们仍然需要解决这个问题,这里有一个用Swift编写的解决方案,可以替换普通的objc_getAssociatedObject,使用自定义的方法来处理弱对象:

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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接