【问题标题】:Using objc_setAssociatedObject with weak references使用带有弱引用的 objc_setAssociatedObject
【发布时间】:2013-05-10 07:23:12
【问题描述】:

我知道 OBJC_ASSOCIATION_ASSIGN 存在,但如果目标对象被释放,它是否将引用归零?还是像过去一样,该引用需要被置为 nil,或者我们以后冒着错误访问的风险?

【问题讨论】:

  • 是的,没错。我试试看。

标签: objective-c weak-references objective-c-runtime


【解决方案1】:

正如超奇迹所证明的那样,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: ?
  • 因为-[NSValue nonretainedObjectValue] 不返回 weak 对象,因此不做归零弱引用。
  • 很酷但小问题: WeakObjectContainer 本身的对象仍然保留,这意味着泄漏。目标对象很可能被收集起来,引用归零,但包装器仍然存在。
【解决方案2】:

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

【讨论】:

  • 不错的解决方案,无需创建自己的包装类。谢谢!
  • 该块应将其返回值注释为_Nullable
【解决方案3】:

尝试后,答案是否定的。

我在 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都是nil,而test3是之前存入test1的指针。使用 test3 会导致尝试访问已被释放的对象。

【讨论】:

  • 那里有错字? “test1”与“test”和“test2”有什么关系?
【解决方案4】:

0xced 的 Swift 版本的答案(支持类型):

public class WeakObjectContainer<T: AnyObject>: NSObject {

    private weak var _object: T?

    public var object: T? {
        return _object
    }

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

}

【讨论】:

    【解决方案5】:

    据我所知,文档或标题中并未指定此行为,因此即使您能够辨别当前行为是什么,它也可能是您不应该指望的实现细节。我猜它是 not 归零的。原因如下:

    一般情况下,在-dealloc 期间,不需要nil 出 iVars 中的引用。如果一个对象被解除分配,它的 iVar 是否被清零并不重要,因为对解除分配的对象或其 iVar 的任何进一步访问本身就是一个编程错误。事实上,我听到一些人争辩说最好在-dealloc 期间清除引用,因为它会使错误访问更加更明显/更快地暴露错误。

    编辑:哦,我想我误读了你的问题。你想要“归零弱引用”。关联存储似乎不支持这些。您可以使用一个标记为 __weak 的 ivar/属性创建一个简单的传递类,并以这种方式实现相同的效果。有点笨拙,但它会工作。

    【讨论】:

    • 我的意思是在我正在查看的情况下,如果我在对象被释放后尝试访问它并不是错误的。如果我有一个弱引用,比如对委托的引用,我希望在我向它发送消息时它可能不再存在。
    【解决方案6】:

    如果人们仍然需要解决这个问题,这里有一个 Swift 解决方案,用自定义的objc_getAssociatedObject 替换普通的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)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-06
      • 1970-01-01
      • 1970-01-01
      • 2014-08-15
      • 2011-09-01
      相关资源
      最近更新 更多