当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?

Posted

技术标签:

【中文标题】当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?【英文标题】:How to safely store an id object in a C++ void* member under ARC when no other references hold on to the object? 【发布时间】:2013-01-24 19:10:22 【问题描述】:

我正在使用 Box2D (C++),我创建了一个 Objective-C 对象并将其分配给 Box2D 主体的 userData 属性,该属性的类型为 void*

现在,在某些情况下,void* userData 可能是对该 ObjC 对象的唯一活动引用。因此,因为我在作业中使用了(__bridge void*),所以 ARC 放手了。这是我需要解决的问题。

我一直在思考如何防止这种情况发生?我阅读了Clang's ARC documentation,特别是关于桥梁铸造的部分(以及关于 SO 的问答)以及对他们认为“不正确”的各种桥梁铸造结构点头。

不过,我的第一个想法是在对 userData 的初始分配中使用 (__bridge_retained void*)。但这让我想知道如何平衡保留?我显然无法向对象发送释放。

所以我必须CFRelease() 对象吗?还是需要CFBridgingRelease()?还是在这里都是非法的?

(__bridge_transfer void*)userData 转换为临时 id 类型是否足够了,也许在之后将 userData 设置为 NULL?这是个好主意吗?

我知道另一种方法是为userData 对象保留一个单独的NSArray/NSDictionary,并使它们与 Box2D 主体的生命周期保持同步,并与它们的 Box2D 主体同步添加和删除它们。

但这感觉有点矫枉过正,因为在这里我知道自己在做什么,我知道只要 Box2D 主体处于活动状态,我就需要 +1 对象,而当 Box2D 主体被移除时,我需要 -1 对象.另外,我知道只有两种方法可以添加和删除 Box2D 主体,并且在我的框架中甚至无法直接访问 userData,因为所有 Box2D 对象都隐藏在 Objective-C 接口/包装器后面。

暂时搁置可能“格式不正确”,您建议我在这种情况下应该怎么做?

【问题讨论】:

@Emil:感谢修复内联代码,我正打算自己做。 没问题,我必须承认在一个 20k 用户的帖子中编辑如此琐碎的事情很奇怪!哈:D 是的,有时我会问一些非常愚蠢的问题:) 【参考方案1】:

__bridge_retained 表示“保留此 ARC 对象,将其发送到非 ARC 区域”。当您需要创建一个“未跟踪的”void * 时,您可以调用它。所以,就你而言,userData = (__bridge_retained void *)obj

__bridge_transfer 的意思是“释放这个物体,把它从非 ARC 的土地上拉回来”。当您想要有效地使void * 无效时,您可以调用它。所以,obj = (__bridge_transfer id)userData。在此之后,userData 指针不能安全使用;相反,您只使用obj。当obj 超出范围时,ARC 将最后一次释放它。这可能需要专门为此目的创建一个临时的id

因此,在您的情况下,您确实希望在将对象发送到 Box2D 时使用 __bridge_retained,并在您希望使 userData 无效时使用 __bridge_transfer。如果您需要以 Objective-C 对象的形式访问 userData,但不使指针无效,请使用普通的 __bridge

【讨论】:

好。我主要对将它的回退感到困惑,因为它似乎没有对对象“做任何事情”,而实际上它确实如此。风格方面,我想我会更喜欢:CFBridgingRelease(body->GetUserData()),然后是 body->SetUserData(NULL)。据我了解,这与 __bridge_transfer 演员表相同。 我自己实际上会使用__bridge_*,因为这与CF没有任何关系。 嗯……是的,但是我需要分配给本地 var,然后还要对该 var 做一些事情(将其设置为 nil),这样编译器就不会抱怨未使用的 var。 有趣,CFBridgingRelease 只是做了一个__bridge_transfer,它只是一个声明为的宏: NS_INLINE id CFBridgingRelease(CFTypeRef CF_CONSUMED X) return (__bridge_transfer id)X; (如果我忽略返回值,编译器不会抱怨未使用的结果)【参考方案2】:

您误解了文档作者对“格式错误”的含义。这是格式错误的:

NSData* data; // Initialized
NSData* data2= (__bridge NSData*) data;

这也是格式错误的:

void* data; // Initialized
void* data2= (__bridge void*) data;

这不是格式错误:

NSData* data; // Initialized
void* data2= (__bridge void*) data;

不病态就足够了,左值是可保留的,右值是不可保留的,反之亦然。因此,由于在您的情况下您将对象指针转换为原始指针,反之亦然,您的方法是正确的。

在您的位置,我将实现一个智能指针,该指针在构造时发送 CFBridgingRetain 消息,在销毁时发送 CFBridgingRelease 消息。

【讨论】:

以上是关于当没有其他引用保留对象时,如何将 id 对象安全地存储在 ARC 下的 C++ void* 成员中?的主要内容,如果未能解决你的问题,请参考以下文章

如何从平面结构有效地建造树木?

如何安全地删除未保存的托管对象?

高并发学习

JVM专题-垃圾回收

JVM专题-垃圾回收

Java 并发系列之十三:安全发布