【问题标题】:objc_setAssociatedObject retain atomic or nonatomicobjc_setAssociatedObject 保留原子或非原子
【发布时间】:2015-04-29 04:16:40
【问题描述】:

当我使用objc_setAssociatedObject时,我知道是使用retain还是assign,但我不知道如何在OBJC_ASSOCIATION_RETAINOBJC_ASSOCIATION_RETAIN_NONATOMIC之间做出决定。什么时候应该使用其中一个?

【问题讨论】:

  • 这可能取决于线程安全是否是一个问题以及调用对象本身是否定义为原子或非原子。这里有一个很好的答案,详细说明了这两者之间的区别:stackoverflow.com/questions/588866/…

标签: ios objective-c objective-c-runtime


【解决方案1】:

执行摘要:如果您可能在一个线程上调用objc_setAssociatedObject,并在另一个线程上同时调用objc_getAssociatedObject,则必须使用OBJC_ASSOCIATION_RETAIN,同时使用相同的objectkey论据。

血腥细节:

你可以在objc-references.mm中查看objc_setAssociatedObject的实现。但实际上OBJC_ASSOCIATION_RETAINOBJC_ASSOCIATION_RETAIN_NONATOMIC 之间的区别只对objc_getAssociatedObject 有影响。

下面是<objc/runtime.h>中这些常量的定义:

enum {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

请注意,014010x0301014030x0303。源代码进一步细分:

enum { 
    OBJC_ASSOCIATION_SETTER_ASSIGN      = 0,
    OBJC_ASSOCIATION_SETTER_RETAIN      = 1,
    OBJC_ASSOCIATION_SETTER_COPY        = 3,            // NOTE:  both bits are set, so we can simply test 1 bit in releaseValue below.
    OBJC_ASSOCIATION_GETTER_READ        = (0 << 8), 
    OBJC_ASSOCIATION_GETTER_RETAIN      = (1 << 8), 
    OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
}; 

所以我们可以看到OBJC_ASSOCIATION_RETAIN_NONATOMIC 只是OBJC_ASSOCIATION_SETTER_RETAIN,但OBJC_ASSOCIATION_RETAIN 实际上是OBJC_ASSOCIATION_SETTER_RETAIN | OBJC_ASSOCIATION_GETTER_RETAIN | OBJC_ASSOCIATION_GETTER_AUTORELEASE

OBJC_ASSOCIATION_GETTER_* 位在_object_get_associative_reference 中检查:

id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

因此,如果您使用OBJC_ASSOCIATION_RETAIN,它将保留并自动释放关联的对象,然后再将其返回给您。但是还有一些不明显的事情正在发生。请注意,该函数创建了一个 AssociationsManager 的本地实例(这是一个存储在堆栈上的 C++ 对象)。这是AssociationsManager的定义:

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { spinlock_lock(&_lock); }
    ~AssociationsManager()  { spinlock_unlock(&_lock); }

    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

所以你可以看到,当函数创建它的AssociationsManager 时,它会获取一个锁,它会一直持有该锁直到管理器被销毁。销毁发生在函数保留了关联对象之后。

为键设置新的关联对象时使用相同的锁。此锁可防止出现竞争条件,即一个线程获取关联对象,而另一个线程替换它并导致对象被释放。

如果您不阻止竞态条件,那么某天您的多线程应用程序会因尝试访问已释放的对象而崩溃(或更糟)。您可以使用OBJC_ASSOCIATION_RETAIN 来防止竞争条件,或者您可以通过其他方式确保您永远不会在一个线程上设置关联,同时在另一个线程上设置关联。

【讨论】:

  • 我遇到了这个确切的比赛条件,你的深入解释帮助我解决了问题,所以我欠你一个感谢!
【解决方案2】:

如果您尝试存储的值将使用 nonatomic 属性(如果它是属性),则使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC,否则使用 OBJC_ASSOCIATION_RETAIN

【讨论】:

    【解决方案3】:

    几乎没有理由使用原子性 (OBJC_ASSOCIATION_RETAIN)。在大多数情况下,线程安全性不能通过使数据访问线程安全来实现。这是一个更大的主题。

    对于属性,原子性的副作用是读取值是自动释放的。但既没有记录关联对象,也没有在 ARC 时代有用。

    所以我的建议是:使用OBJC_ASSOCIATION_RETAIN_NONATOMIC

    【讨论】:

      猜你喜欢
      • 2012-03-18
      • 1970-01-01
      • 2012-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-26
      相关资源
      最近更新 更多