Objective-C 自动引用计数 (ARC)¶
关于本文档¶
目的¶
本文档的首要目的是作为自动引用计数的完整技术规范。对于给定的核心 Objective-C 编译器和运行时,应该可以编写一个编译器和运行时来实现这些新的语义。
其次,本文档旨在作为解释 ARC 为什么以这种方式设计的依据。这应该严格地集中在技术设计上,不应涉及营销推测。
背景¶
本文档假设读者熟悉 C 语言。
块 是 C 语言扩展,用于创建匿名函数。用户使用 块指针 与块对象交互和传输,块指针表示形式与普通指针相同。块可以捕获来自局部变量的值;当发生这种情况时,必须动态分配内存。初始分配是在堆栈上完成的,但运行时提供了一个 Block_copy
函数,该函数在给定块指针时,要么将底层块对象复制到堆上,将它的引用计数设置为 1 并返回新的块指针,要么(如果块对象已经在堆上)将它的引用计数增加 1。成对的函数是 Block_release
,它将引用计数减少 1,并在计数达到零且在堆上时销毁对象。
Objective-C 是一组语言扩展,足够重要,可以被认为是一种不同的语言。它是 C 的严格超集。这些扩展也可以应用于 C++,产生一种称为 Objective-C++ 的语言。主要特性是单继承对象系统;我们简要介绍一下现代方言。
Objective-C 定义了一种新的类型种类,统称为 对象指针类型。这种类型有两个著名的内置成员,id
和 Class
;id
是所有对象指针的最终超类型。对象指针类型之间转换的有效性在运行时不会检查。用户可以定义 类;每个类都是一种类型,指向该类型的指针是对象指针类型。一个类可以有一个超类;它的指针类型是其超类指针类型的一个子类型。一个类具有一组 实例变量,这些字段出现在该类的所有实例上。对于每个类 T,都与之关联一个元类;它没有字段,它的超类是 T 的超类的元类,它的元类是一个全局类。每个类都拥有一个全局对象,其类是该类的元类;元类没有关联类型,因此指向该对象的指针具有类型 Class
。
类声明(@interface
)声明了一组 方法。一个方法具有返回值类型、参数类型列表和一个 选择器:一个像 foo:bar:baz:
这样的名称,其中冒号的数量对应于形式参数的数量。一个方法可以是实例方法,在这种情况下它可以在类的对象上调用,也可以是类方法,在这种情况下它可以在元类的对象上调用。可以通过提供一个对象(称为 接收者)和一个形式参数列表来调用方法,这些形式参数与选择器交织在一起,如下所示
[receiver foo: fooArg bar: barArg baz: bazArg]
这在接收者的动态类中查找具有此名称的方法,然后在该类的超类中查找,依此类推,直到找到可以执行的东西为止。接收者“表达式”也可能是类的名称,在这种情况下,实际接收者是该类的类对象,或者(在方法定义中)它可能是 super
,在这种情况下,查找算法从静态超类而不是动态类开始。在类中动态找到的实际方法不是在 @interface
中声明的那些,而是在单独的 @implementation
声明中定义的那些;但是,在编译调用时,类型检查是根据 @interface
中声明的方法完成的。
方法声明也可以分组到 协议 中,协议本质上不与任何类相关联,但类可以声明它们遵循协议。对象指针类型可以使用对象已知支持的其他协议进行限定。
类扩展 是实例变量和方法的集合,旨在允许将类的 @interface
分布到多个文件中;但是,仍然存在一个主要实现文件,它必须看到所有类扩展的 @interface
。类别 允许在任意类上事后声明方法(但不是实例变量);类别 @implementation
中的方法将在类别在运行时加载时动态添加到该类的动态方法表中,如果发生冲突,则替换这些方法。
在标准环境中,对象是在堆上分配的,它们的生命周期是通过引用计数手动管理的。这是通过两个实例方法完成的,所有类都应该实现这两个方法:retain
将对象的引用计数增加 1,而 release
将其减少 1,并在计数达到 0 时调用实例方法 dealloc
。为了简化某些操作,还有一个 自动释放池,这是一个线程局部列表,用于在稍后对其中的对象调用 release
;可以通过对对象调用 autorelease
将其添加到此池中。
块指针可以转换为类型 id
;块对象以一种使它们与 Objective-C 对象兼容的方式布局。有一个内置类,所有块对象都被认为是该类的对象;这个类通过调整引用计数来实现 retain
,而不是通过调用 Block_copy
。
演变¶
ARC 不断发展,本文档必须随着语言的进步而更新。
如果更改提高了语言的表达能力,例如通过解除限制或添加新语法,则更改将使用修订标记进行注释,如下所示
ARC 适用于 Objective-C 指针类型、块指针类型和 [从 Apple 8.0、LLVM 3.8 开始] 在
extern "BCPL"
块中声明的 BPTR。
目前,通过其唯一实现(及其宿主项目)clang 的版本来对该文档进行版本控制是明智的。“LLVM X.Y” 指的是 LLVM 项目中 clang 的开源版本。“Apple X.Y” 指的是 Apple 提供的 Apple LLVM 编译器的版本。其他准备他们自己独立版本化的 clang 版本并希望在本文档中维护类似信息的组织应向 cfe-dev 发送请求。
如果更改降低了语言的表达能力,例如通过强加新的限制,这应该被视为原始规范中的疏忽,并且应该在所有版本中避免。此类更改通常应该避免。
常规¶
自动引用计数 (ARC) 为 Objective-C 对象和块实现了自动内存管理,使程序员无需显式插入保留和释放操作。它不提供循环收集器;用户必须显式管理其对象的生存期,手动或使用弱引用或不安全引用来打破循环。
可以使用编译器标志 -fobjc-arc
显式启用 ARC。也可以使用编译器标志 -fno-objc-arc
显式禁用 ARC。编译行中最后出现的这两个标志中的“获胜”。
如果启用了 ARC,__has_feature(objc_arc)
将在预处理器中扩展为 1。有关 __has_feature
的更多信息,请参阅 语言扩展 文档。
可保留对象指针¶
本节介绍可保留对象指针、其基本操作以及 ARC 对其使用施加的限制。特别要注意,它涵盖了指针 *值*(表示指向对象的位模式)的规则,而不是指针 *对象*(存储指针值的内存位置)。对象的规则在下一节中介绍。
一个 可保留对象指针(或“可保留指针”)是 可保留对象指针类型(“可保留类型”)的值。可保留对象指针类型有三种
块指针(通过将插入符 (
^
) 声明符符号应用于函数类型来形成)Objective-C 对象指针 (
id
、Class
、NSFoo*
等)用
__attribute__((NSObject))
标记的 typedef
其他指针类型,例如 int*
和 CFStringRef
,不受 ARC 语义和限制的约束。
基本原理
我们没有义务要求所有代码都使用 ARC 重新编译;因此,ARC 必须与手动管理保留和释放操作的 Objective-C 代码互操作。一般来说,为了使编译器支持的引用计数系统提供可靠的互操作性,有三个要求
类型系统必须可靠地识别哪些对象要进行管理。一个
int*
可能是一个指向malloc
分配的数组的指针,也可能是一个指向该数组的内部指针,或者可能指向某个字段或局部变量。相反,可保留对象指针类型的值永远不是内部的。类型系统必须可靠地指示如何管理某一类型的对象。这通常意味着该类型必须暗示一个用于递增和递减保留计数的程序。支持单一所有权对象需要在语言中进行更多显式调解。
必须有可靠的约定来确定调用者和被调用者之间是否以及何时传递“所有权”,对于参数和返回值都是如此。Objective-C 方法遵循这种约定非常可靠,至少对于 macOS 上的系统库而言,函数总是按 +0 传递对象。另一方面,用于 Core Foundation 对象的基于 C 的 API 具有更多不同的传输语义。
不建议使用 __attribute__((NSObject))
typedef。如果绝对有必要使用此属性,请非常明确地使用 typedef,并且不要假设它将被语言功能(如 __typeof
和 C++ 模板参数替换)保留。
基本原理
任何偶然从类型中剥离类型“糖”的编译器操作都会产生一个没有属性的类型,这可能会导致意外行为。
保留计数语义¶
可保留对象指针要么是 空指针,要么是指向有效对象的指针。此外,如果它具有块指针类型且不是 null
,则它实际上必须是指向块对象的指针,如果它具有 Class
类型(可能具有协议限定),则它实际上必须是指向类对象的指针。否则,只要实现的方法遵循静态类型的签名,ARC 就不强制执行 Objective-C 类型系统。如果 ARC 遇到无效指针,则行为未定义。
出于 ARC 的目的,有效对象是指具有“行为良好”的保留操作的对象。具体来说,该对象必须布局良好,以便 Objective-C 消息发送机制能够成功地向其发送以下消息
retain
,不接受任何参数并返回指向该对象的指针。release
,不接受任何参数并返回void
。autorelease
,不接受任何参数并返回指向该对象的指针。
这些方法的行为受到以下方式的约束。术语 高级语义 是一个故意含糊的术语;意图是程序员必须以一种方式实现这些方法,以便编译器根据这些约束修改代码为安全的,不会违反他们的要求。例如,如果用户在 retain
中放置日志语句,他们不应该对这些语句根据优化设置执行的频率或多或少感到意外。这些约束不是优化机会的穷举:局部变量中保存的值会受到额外的限制,将在本文档后面介绍。
如果一个计算历史特征发送了 retain
之后紧接着发送了 release
给同一个对象,在其中没有任何对该对象的中间 release
,在高级语义下不等于一个计算历史,其中这些发送被删除,则行为未定义。请注意,这意味着这些方法可能不会引发异常。
如果一个计算历史特征在完成一个发送 release
之后对一个对象的任何使用,而这个 release
之前没有发送 retain
给同一个对象,则行为未定义。
autorelease
的行为必须等效于在当前范围内的自动释放池之一被弹出时发送 release
。它可能不会引发异常。
当语义要求对可保留对象指针执行其中一项操作时,如果该指针是 null
,则效果为空操作。
本文档中描述的所有语义都受到额外的 优化规则 的约束,这些规则允许基于数据流的本地知识来删除或优化操作。语义描述了编译器实现的高级行为,而不是程序将被编译成的一系列确切操作。
可保留对象指针作为操作数和参数¶
一般来说,ARC 在将可保留对象指针简单地用作表达式中的操作数时不执行保留或释放操作。这包括
从具有非弱 所有权 的对象中加载可保留指针,
将可保留指针作为参数传递给函数或方法,以及
接收可保留指针作为函数或方法调用的结果。
基本原理
虽然这可能看起来没有争议,但当多个表达式“并行”求值时,实际上是不安全的,例如二元运算符和调用,因为(例如)一个表达式可能会从对象中加载,而另一个表达式可能会写入对象。但是,C 和 C++ 已经将此称为未定义行为,因为求值是无序的,ARC 只是利用了这一点,以避免在大量调用中需要保留参数。
本节的其余部分介绍了这些规则的例外情况、如何检测这些例外情况以及这些例外情况在语义上的含义。
消耗的参数¶
可保留对象指针类型的函数或方法参数可以标记为 消耗,表示被调用者期望获取一个 +1 保留计数的所有权。这可以通过将 ns_consumed
属性添加到参数声明中来完成,如下所示
void foo(__attribute((ns_consumed)) id x);
- (void) foo: (id) __attribute((ns_consumed)) x;
此属性是函数或方法的类型的一部分,而不是参数的类型。它仅控制参数如何传递和接收。
传递此类参数时,ARC 会在进行调用之前保留该参数。
接收此类参数时,ARC 会在函数结束时释放该参数,前提是受局部值的常规优化影响。
基本原理
这正式化了从调用者到被调用者的所有权直接传递。这里最常见的场景是将 self
参数传递给 init
,但将其推广是有用的。通常,局部优化将删除任何额外的保留和释放:在调用者一方,保留操作将与 +1 源合并,在被调用者一方,释放操作将被合并到参数的初始化中。
方法的隐式self
参数可以通过在方法声明中添加__attribute__((ns_consumes_self))
来标记为已消耗。在init
族中的方法被视为隐式地标记了此属性。
如果对具有ns_consumed
参数(除 self 外)的方法进行 Objective-C 消息发送时接收者为空,则行为未定义。如果 Objective-C 消息发送静态解析到的方法与动态解析到的方法具有不同的ns_consumed
参数集,则行为未定义。如果通过具有不同ns_consumed
参数集的静态类型进行块或函数调用,而被调用块或函数的实现具有不同的ns_consumed
参数集,则行为未定义。
基本原理
具有空接收者的消耗参数是保证的泄漏。消耗参数的不匹配会导致过度保留或过度释放,具体取决于方向。有关函数调用的规则实际上只是对现有 C/C++ 规则的应用,该规则涉及通过不兼容的函数类型调用函数,但明确说明它很有用。
保留的返回值¶
返回可保留对象指针类型的函数或方法可以标记为返回保留的值,表示调用者希望获得 +1 保留计数的所有权。这可以通过在函数或方法声明中添加ns_returns_retained
属性来完成,如下所示
id foo(void) __attribute((ns_returns_retained));
- (id) foo __attribute((ns_returns_retained));
此属性是函数或方法类型的组成部分。
从这样的函数或方法返回时,ARC 会在离开所有局部范围之前,在返回语句的求值点保留该值。
从这样的函数或方法接收返回值时,ARC 会在包含它的完整表达式的末尾释放该值,这取决于对局部值的常规优化。
基本原理
这正式化了从被调用者到调用者的所有权直接转移。此模型最常见的场景是init
、alloc
、new
和copy
方法的保留返回值,但框架中还有其他情况。在优化之后,通常不需要额外的保留和释放操作。
在alloc
、copy
、init
、mutableCopy
和new
族中的方法被隐式地标记为__attribute__((ns_returns_retained))
。这可以通过显式地将方法标记为__attribute__((ns_returns_not_retained))
来抑制。
如果 Objective-C 消息发送静态解析到的方法与其动态解析到的方法在结果上的保留语义不同,则行为未定义。如果通过具有不同结果保留语义的静态类型进行块或函数调用,而被调用块或函数的实现具有不同的结果保留语义,则行为未定义。
基本原理
返回值不匹配会导致过度保留或过度释放,具体取决于方向。同样,有关函数调用的规则实际上只是对现有 C/C++ 规则的应用,该规则涉及通过不兼容的函数类型调用函数。
未保留的返回值¶
返回可保留对象类型但未返回保留值的函数或方法必须确保该对象在返回边界处仍然有效。
从这样的函数或方法返回时,ARC 会在返回语句的求值点保留该值,然后离开所有局部范围,然后在确保该值跨越调用边界时,平衡保留操作。在最坏的情况下,这可能涉及autorelease
,但调用者不应假设该值实际上位于自动释放池中。
ARC 在调用者端不执行任何额外的强制工作,尽管它可以选择执行某些操作来缩短返回值的生存期。
基本原理
在非 ARC 代码中,通常不返回自动释放的值;因此,该约定不会强制执行任何一条路径。不需要执行不必要的保留和自动释放操作很方便;这允许进行优化,例如在可以证明原始指针在返回点仍然有效的情况下省略保留/自动释放操作。
函数或方法可以使用__attribute__((ns_returns_autoreleased))
标记,以指示它返回一个指针,该指针至少在最内部的自动释放池生存期内保证有效。在定义此类方法时,不会强制执行其他语义;它只是使调用者能够进行优化。
桥接转换¶
一个桥接转换是一个 C 样式的转换,用三个关键字之一进行注释
(__bridge T) op
将操作数转换为目标类型T
。如果T
是可保留的对象指针类型,则op
必须具有不可保留的指针类型。如果T
是不可保留的指针类型,则op
必须具有可保留的对象指针类型。否则,该转换将是非法的。不会进行所有权转移,并且 ARC 不会插入任何保留操作。(__bridge_retained T) op
将操作数(必须具有可保留的对象指针类型)转换为目标类型(必须是不可保留的指针类型)。ARC 会保留该值,这取决于对局部值的常规优化,并且接收者有责任平衡该 +1。(__bridge_transfer T) op
将操作数(必须具有不可保留的指针类型)转换为目标类型(必须是可保留的对象指针类型)。ARC 会在包含的完整表达式的末尾释放该值,这取决于对局部值的常规优化。
这些转换是在将对象传入和传出 ARC 控制时所需的;有关原因,请参阅有关可保留对象指针的转换的部分。
仅使用__bridge_retained
或__bridge_transfer
转换来让 ARC 分别发出不平衡的保留或释放操作,这是一种不好的做法。
限制¶
可保留对象指针的转换¶
通常,尝试将可保留对象指针类型的值隐式或显式转换为任何不可保留类型或反之的程序将是非法的。例如,不能将 Objective-C 对象指针转换为void*
。作为例外,允许转换为intptr_t
,因为此类转换不会进行所有权转移。桥接转换可用于在必要时执行这些转换。
基本原理
如果可以将对象随意作为未管理类型传递,我们无法确保对象的生存期得到正确管理。提供桥接转换,以便程序员可以明确描述转换是否将控制权传递到 ARC 或从 ARC 传递出去。
但是,以下例外情况适用。
具有已知语义的表达式的可保留对象指针类型转换¶
[Apple 4.0 版开始,LLVM 3.1 版开始] 这些例外情况已大大扩展;以前仅适用于难以分类的更小的子集,但其中包括空指针、消息发送(在给定规则下)以及各种全局常量。
如上所述,从除可保留对象指针类型以外的类型到可保留对象指针类型的未桥接转换是非法的,除非转换的操作数具有已知保留、已知未保留或已知保留无关的语法形式。
如果表达式是
Objective-C 字符串文字,
从
const
系统全局变量加载C 可保留指针类型,或空指针常量。
则表达式为已知保留无关。
如果表达式是C 可保留指针类型的右值,并且是
对函数的直接调用,并且该函数具有
cf_returns_not_retained
属性,或者它是经过审核的函数,不具有cf_returns_retained
属性,也不遵循创建/复制命名约定,消息发送,并且声明的方法具有
cf_returns_not_retained
属性,或者既不具有cf_returns_retained
属性,也不具有暗示保留结果的选择器族,或
[LLVM 3.6 版开始] 从 const
非系统全局变量加载。
如果发送的消息调用了一个声明的方法,并且该方法具有
cf_returns_retained
属性,或者它没有cf_returns_not_retained
属性,但它具有 选择器族,暗示了保留的结果。
此外
逗号表达式根据其右侧进行分类,
语句表达式根据其结果表达式进行分类,如果存在的话,
应用于 Objective-C 属性左值的左值到右值转换根据底层消息发送进行分类,并且
条件运算符根据其第二个和第三个操作数进行分类,如果它们在分类上一致,或者如果其中一个已知为保留无关,则根据另一个进行分类。
如果强制转换操作数已知保留,则转换将被视为 __bridge_transfer
强制转换。如果强制转换操作数已知未保留或已知保留无关,则转换将被视为 __bridge
强制转换。
基本原理
桥接强制转换很烦人。然而,在没有完全自动化管理 CF 对象的能力的情况下,我们只能进行相对糟糕的尝试,以减少对大量显式桥接的需求。因此有了这些规则。
到目前为止,我们有意避免将函数调用中返回的保留的 CF 结果隐式转换为 __bridge_transfer
强制转换。令人担忧的是,某些代码模式——例如,创建一个 CF 值,将其分配给一个 ObjC 类型的局部变量,然后在完成时调用 CFRelease
——很可能被意外接受,从而导致神秘的行为。
对于从 const
全局变量加载 C 可保留指针类型,可以合理地假设全局系统常量已使用真正的常量(例如字符串文字)初始化,但用户常量可能已使用动态分配的某些内容初始化,使用全局初始化器。
在某些情况下从可保留对象指针类型进行转换¶
[自 Apple 4.0,LLVM 3.1 起]
如果可保留对象指针类型的表达式的结果被显式转换为 C 可保留指针类型,除非结果被立即用于
初始化 Objective-C 消息发送中的参数,其中参数没有用
cf_consumed
属性标记,或初始化对 已审核 函数的直接调用中的参数,其中参数没有用
cf_consumed
属性标记。
基本原理
已消耗的参数被排除在外,因为 ARC 会自然地用保留来平衡它们,这被认为过于危险。部分原因是,几个最常见的消耗函数都在 Release
家族中,并且如果显式释放被以这种方式静默地平衡掉,将非常不幸。
所有权限定¶
本节描述了可保留对象指针类型的对象的行为;也就是说,存储可保留对象指针的内存位置。
如果类型是可保留对象指针类型或其元素类型是可保留对象所有者类型的数组类型,则该类型是 可保留对象所有者类型。
所有权限定符是一种类型限定符,它仅适用于可保留对象所有者类型。数组类型根据其元素类型进行所有权限定,并将所有权限定符添加到数组类型将对其元素类型进行限定。
如果程序尝试将所有权限定符应用于已经所有权限定的类型,即使是相同的限定符,程序也是非法的。此规则有一个例外:所有权限定符可以应用于替换的模板类型参数,它会覆盖模板参数提供的所有权限定符。
在形成函数类型时,将调整结果类型,以便删除任何顶层所有权限定符。
除非在 推断规则中描述,否则如果程序尝试形成对没有所有权限定符的可保留对象所有者类型的指针或引用类型,则程序是非法的。
基本原理
这些规则,连同推断规则,确保所有可保留对象指针类型的对象和左值都有所有权限定符。在模板替换期间覆盖所有权限定符的能力是必要的,以抵消 对模板类型参数推断 __strong。返回类型上的所有权限定符被丢弃,因为它们在那里没有任何作用,除了会导致与重载和模板有关的虚假问题。
有四个所有权限定符
__autoreleasing
__strong
__unsafe_unretained
__weak
如果类型用 __autoreleasing
、__strong
或 __weak
限定,则该类型是 非平凡所有权限定。
拼写¶
所有权限定符的名称为实现保留。程序不能假设它们是或不是用宏实现的,或者这些宏扩展为什麽。
所有权限定符可以写入任何其他类型限定符可以写入的位置。
如果所有权限定符出现在声明说明符中,则应用以下规则
如果类型说明符是可保留对象所有者类型,则限定符最初适用于该类型;
否则,如果最外层非数组声明符是指针或块指针声明符,则限定符最初适用于该类型;
否则程序是非法的。
如果限定符在声明中如此应用,其中下一个最内层声明符是函数声明符,并且该函数声明符中有一个块声明符,那么限定符将应用于该块声明符,并且从新位置开始重新考虑此规则。
如果所有权限定符出现在声明符名称上或在声明的对象上,则它将应用于最内层指针或块指针类型。
如果所有权限定符出现在声明符中的其他任何位置,则它适用于那里的类型。
基本原理
所有权限定符类似于 const
和 volatile
,因为它们可能在声明符中多个不同的位置有意义地应用。但是,与这些限定符不同,在许多情况下它们没有意义,因此我们努力将限定符“移动”到有意义的位置。总体目标是允许程序员编写,比如,__strong
在整个声明之前,并使其应用于最左侧的有意义的位置。
属性声明¶
可保留对象指针类型的属性可能具有所有权。如果属性的类型是所有权限定的,则该属性具有该所有权。如果属性具有以下任何修饰符,则该属性具有相应的权属。如果属性具有冲突的所有权来源,或者具有冗余的所有权修饰符,或者具有 __autoreleasing
所有权,则该属性是非法的。
assign
暗示__unsafe_unretained
所有权。copy
暗示__strong
所有权,以及设置器上副本语义的通常行为。retain
暗示__strong
所有权。strong
暗示__strong
所有权。unsafe_unretained
暗示__unsafe_unretained
所有权。weak
暗示__weak
所有权。
除了 weak
之外,这些修饰符在非 ARC 模式下可用。
属性的指定所有权保存在其元数据中,但否则意义纯粹是惯例,除非属性被合成。如果属性被合成,则 关联实例变量是 @synthesize
声明可能隐式命名的实例变量。如果关联的实例变量已经存在,则其所有权限定必须等于属性的所有权;否则,实例变量将使用该所有权限定创建。
没有所有权来源的合成可保留对象指针类型的属性具有其关联实例变量的所有权,如果它已经存在;否则,[自 Apple 3.1,LLVM 3.1 起] 其所有权隐式为 strong
。在此修订之前,合成此类属性是非法的。
基本原理
默认使用 strong
是安全的,并且与关于 推断所有权的通用 ARC 规则一致。不幸的是,它与非 ARC 规则不一致,该规则指出此类属性隐式为 assign
。然而,该规则在 ARC 中显然是站不住脚的,因为它会导致默认情况下不安全的代码。禁止属性的主要优点是避免与非 ARC 实践混淆,我们最终认为这不足以证明需要额外的语法(更重要的是)迫使新手理解所有权规则才能声明一个属性,而默认情况下是如此合理。将规则更改为非 ARC 实践是可以接受的,因为我们保守地禁止了合成,以便给自己 exactly this leeway。
将 __attribute__((NSObject))
应用于非可保留对象指针类型的属性,其行为与 ARC 之外的行为相同:它要求属性类型为某种指针,并允许使用除 assign
以外的修饰符。这些修饰符只影响合成的 getter 和 setter;对 ivar 的直接访问(即使是合成的)仍然具有基本语义,并且 ivar 中的值不会在释放时自动释放。
语义¶
对于可保留对象指针类型的对象,可以执行五种 托管操作。每个限定符为每种操作指定不同的语义。在对象生命周期之外访问对象仍然是未定义的行为。
具有“基本语义”的加载或存储与在具有相同对齐方式和非所有权限定符的 void*
左值上进行的相应操作的语义相同。
读取 在对对象左值执行左值到右值转换时发生。
对于
__weak
对象,当前指向的对象会被保留,并在当前完整表达式的末尾释放。特别地,向__weak
对象发送消息会使对象保留到完整表达式的末尾。__weak MyObject *weakObj; void foo() { // weakObj is retained before the message send and released at the end of // the full expression. [weakObj m]; }
这必须与对指向对象的赋值以及指向对象的最终释放原子地执行。
对于所有其他对象,左值使用基本语义加载。
赋值 在计算赋值运算符时发生。语义根据限定符的不同而有所不同。
对于
__strong
对象,首先保留新指向的对象;其次,使用基本语义加载左值;第三,使用基本语义将新指向的对象存储到左值中;最后,释放旧指向的对象。这不是原子地执行的;必须使用外部同步来使其在面对并发加载和存储时安全。对于
__weak
对象,左值会更新为指向新指向的对象,除非新指向的对象是当前正在被释放的对象,在这种情况下,左值会更新为指向空指针。这必须与对该对象的其它赋值、对该对象的读取以及对新指向对象的最终释放原子地执行。对于
__unsafe_unretained
对象,使用基本语义将新指向的对象存储到左值中。对于
__autoreleasing
对象,新指向的对象会被保留、自动释放,并使用基本语义存储到左值中。
初始化 在对象生命周期开始时发生,这取决于其存储持续时间。初始化分两个阶段进行。
首先,使用基本语义将空指针存储到左值中。如果对象是
__unsafe_unretained
,则会跳过此步骤。其次,如果对象有初始化程序,则会计算该表达式,然后使用通常的赋值语义将其赋值到对象中。
销毁 在对象生命周期结束时发生。在所有情况下,它在语义上等同于将空指针赋值给对象,前提是当然不能在对象生命周期结束之后合法地读取该对象。
移动 在特定的情况下发生,其中左值被“移动”,意味着其当前指向的对象将被使用,但该对象可能处于不同的(但仍然有效的)状态。这出现在 C++ 中的 __block
变量和右值引用中。对于 __strong
左值,移动等同于使用基本语义加载左值,使用基本语义向其写入空指针,然后在当前完整表达式的末尾释放加载结果。对于所有其他左值,移动等同于读取对象。
限制¶
__autoreleasing
对象的存储持续时间¶
如果程序声明了非自动存储持续时间的 __autoreleasing
对象,则该程序格式不正确。如果程序在块中捕获 __autoreleasing
对象,或者(除非通过引用)在 C++11 lambda 中捕获 __autoreleasing
对象,则该程序格式不正确。
基本原理
自动释放池本质上与当前线程和范围绑定。虽然可以创建实例变量被自动释放对象填充的临时对象,但 ARC 无法提供任何安全保证。
如果在自动释放池在范围内时将非空指针赋值给 __autoreleasing
对象,然后在自动释放池范围退出之后读取该对象,则行为未定义。
指针到所有权限定类型转换¶
如果程序将类型为 T*
的表达式显式或隐式转换为类型为 U*
的表达式,其中 T
和 U
具有不同的所有权限定符,则该程序格式不正确,除非
T
使用__strong
、__autoreleasing
或__unsafe_unretained
限定,并且U
使用const
和__unsafe_unretained
同时限定;或者T
或U
为cv void
,其中cv
是可选的非所有权限定符序列;或者转换是使用 Objective-C++ 中的
reinterpret_cast
请求的;或者转换是合法的 按写回传递。
在 Objective-C++ 中,类似的规则适用于 T&
和 U&
。
基本原理
这些规则为间接指针提供了合理的类型安全级别,只要底层内存没有被释放。允许转换为 const __unsafe_unretained
,因为所有这些所有权语义的读取语义都是等效的,这是一个非常有用和常见的模式。与 void*
的相互转换对于分配内存或以其他方式逃避类型系统很有用,但请谨慎使用。 reinterpret_cast
被认为是承担任何问题责任的明显信号。
通过不同限定类型的左值访问所有权限定对象是未定义的行为,但任何非 __weak
对象都可以通过 __unsafe_unretained
左值读取。
如果在对对象执行第一个托管操作之前,__strong
或 __weak
对象的存储没有被正确地初始化,或者如果在对象被正确地销毁之前释放或重新使用该对象的存储,则行为未定义。 __strong
或 __weak
对象的存储可以通过用空指针的表示填充它来正确地初始化,例如,通过使用 calloc
获取内存或使用 bzero
将其清零。可以通過將空指標賦值給 __strong
或 __weak
对象来正确地销毁它。 __strong
对象还可以通过复制其存储来正确地初始化 (例如,使用 memcpy
),复制不同 __strong
对象的表示,其存储已被正确地初始化;正确地执行此操作会销毁源对象,并导致其存储不再被正确地初始化。 __weak
对象不能以这种方式复制表示。
对于初始化和销毁由 ARC 控制的对象,会自动遵循这些要求。
静态、自动和临时存储持续时间的对象
Objective-C 对象的实例变量
数组的元素,其中数组对象的初始化和销毁由 ARC 控制
Objective-C 结构类型的字段,其中结构对象的初始化和销毁由 ARC 控制
Objective-C++ 非联合类类型的非静态数据成员
使用
new
或new[]
运算符创建并使用相应的delete
或delete[]
运算符销毁的动态存储持续时间的 Objective-C++ 对象和数组
对于这些对象,不会自动遵循这些要求。
在其他内存中创建的具有动态存储时长的对象,例如由
malloc
返回的内存。联合体成员。
基本原理
ARC 在初始化对象和销毁对象时必须执行特殊操作。在许多常见情况下,ARC 知道对象何时创建以及何时销毁,并能确保这些操作正确执行。但是,在其他情况下,ARC 需要程序员的配合来建立其初始化不变性,因为 ARC 无法动态推断这些不变性是否完好。例如,在 C 中,用于初始化变量的赋值和用于替换存储在该变量中的现有值的赋值在语法上没有区别,但 ARC 必须执行一项或另一项操作。ARC 选择始终假设对象已初始化(除非 ARC 负责初始化它们),因为唯一可行的替代方案是禁止所有可能用于访问未初始化内存的代码模式,而这将过于限制。在实践中,这很少是一个问题,因为程序员通常不需要处理需求无法自动处理的对象。
请注意,在非 ARC 代码中,动态分配的非平凡所有权限定类型的 Objective-C++ 数组与非 ARC 代码不兼容,因为非 ARC 代码将元素类型视为 POD。在 ARC 翻译单元中使用 new[]
创建的此类数组不能在非 ARC 翻译单元中使用 delete[]
删除,反之亦然。
通过回写传递给输出参数¶
如果传递给类型为 T __autoreleasing *
的参数的参数的类型为 U oq *
,其中 oq
是所有权限定符,那么如果满足以下条件,该参数是“按回写传递”的候选者:
oq
为__strong
或__weak
,并且使用
U __strong *
初始化T __strong *
是合法的。
为了进行重载解析,需要按回写传递的隐式转换序列始终比不需要按回写传递的隐式转换序列更糟糕。
如果参数表达式没有合法的形式,则按回写传递是非法的:
&var
,其中var
是具有可保留对象指针类型的自动存储时长的标量变量。条件表达式,其中第二和第三个操作数都是合法的形式。
其操作数为合法形式的强制转换。
空指针常量。
基本原理
参数形式的限制有两个目的。首先,它使将数组的地址传递给参数成为不可能,这有助于防止错误地将“数组”参数推断为输出参数,否则会带来严重的风险。其次,它大大降低了用户由于以下实现(见下文)而看到令人困惑的别名问题的可能性,在以下实现中,它们对回写临时变量的存储不会立即在原始参数变量中看到。
按回写传递的评估方式如下:
评估参数,以产生类型为
U oq *
的指针p
。如果
p
是一个空指针,则传递一个空指针作为参数,并且按回写传递不需要进一步的操作。否则,将创建一个类型为
T __autoreleasing
的临时变量,并将其初始化为空指针。如果该参数不是标记为
out
的 Objective-C 方法参数,则将读取*p
,并将结果使用原始语义写入临时变量。将临时变量的地址作为参数传递给实际的调用。
调用完成后,将使用原始语义加载临时变量,并将该值分配给
*p
。
基本原理
这确实是相当复杂。在理想的情况下,我们将看到局部变量被传递给输出参数,并对其类型进行追溯修改,使其成为 __autoreleasing
而不是 __strong
。这将非常困难,并且在 C 类型系统下并不总是站得住脚。但是,要求程序员在他们打算用于输出参数的所有变量上编写 __autoreleasing
被认为是不可接受的入侵。这是最不糟糕的解决方案。
结构体和联合体的所有权限定字段¶
结构体或联合体的成员可以声明为具有所有权限定类型。如果类型限定为 __unsafe_unretained
,则包含聚合体的语义与非 ARC 模式下非限定类型的语义相同。如果类型限定为 __autoreleasing
,则程序是非法的。否则,如果类型是非平凡所有权限定类型,则将应用其他规则。
Objective-C 和 Objective-C++ 都支持非平凡所有权限定字段。由于标准之间存在正式差异,因此正式处理方式不同;但是,基本语言模型旨在对于相同的代码保持一致。
基本原理
在聚合类型中允许 __strong
和 __weak
引用,使程序员能够利用 C 和 C++ 的正常语言工具,同时仍然自动管理内存。虽然通常使用 Objective-C 对象作为辅助数据结构更简单也更符合习惯,但这样做会引入额外的分配和消息发送开销,这会导致无法接受的性能。使用结构体可以解决一些这种矛盾。
__autoreleasing
被禁止,因为依赖自动释放作为所有权工具(在函数局部上下文中除外)是危险的。
Clang 的早期版本只允许在 Objective-C++ 类中使用 __strong
和 __weak
引用,而不是在 Objective-C 中。这种限制是由于向 C 添加对非平凡结构体类型支持的复杂性而产生的一个不受欢迎的短期约束。
在 Objective-C++ 中,非平凡所有权限定类型在几乎所有方面都与具有非平凡默认构造函数、复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符和析构函数的类类型相同。这包括确定具有此类类型的非静态数据成员的类的特殊成员的平凡性。
在 Objective-C 中,定义不能如此简洁:由于 C 标准缺乏对非平凡类型的规则,因此必须首先制定这些规则。它们将在下一节中给出。目的是使这些规则与 C++ 中的规则在很大程度上保持一致,以便在两种语言中都可以表达代码。
C 中非平凡类型的正式规则¶
以下是一些基本规则,可以添加到 C 中以支持实现定义的非平凡类型。
如果满足以下条件,则 C 中的类型被称为“非平凡复制”、“非平凡销毁”或“非平凡默认初始化”:
它是一个结构体或联合体,包含一个成员,该成员的类型是非平凡(分别地)复制、销毁或默认初始化。
它是一个限定类型,其非限定类型是非平凡(分别地)复制、销毁或默认初始化(至少对于标准 C 限定符)。
它是一个数组类型,其元素类型是非平凡(分别地)复制、销毁或默认初始化。
如果满足以下条件,则 C 中的类型被称为“非法复制”、“非法销毁”或“非法默认初始化”:
它是一个联合体,包含一个成员,该成员的类型是非法或非平凡(分别地)复制、销毁或初始化。
它是一个限定类型,其非限定类型是非法(分别地)复制、销毁或默认初始化(至少对于标准 C 限定符)。
它是一个数组类型,其元素类型是非法(分别地)复制、销毁或默认初始化。
C 标准规则下描述的任何类型都不应是非平凡或非法复制、销毁或默认初始化。实现可以提供具有这些属性之一或更多属性的附加类型。
如果表达式满足以下条件,则它需要复制一个类型:
将该类型的参数传递给函数调用。
定义一个声明该类型参数的函数。
调用或定义一个返回该类型值的函数。
赋值给该类型的左值。
将该类型的左值转换为右值。
如果程序满足以下条件,则它需要销毁一个类型:
将该类型的参数传递给函数调用。
定义一个声明该类型参数的函数。
调用或定义一个返回该类型值的函数。
创建该类型的自动存储时长对象。
赋值给该类型的左值。
将该类型的左值转换为右值。
如果程序满足以下条件,则它需要默认初始化一个类型:
声明该类型的变量,但不带初始化程序。
如果表达式需要复制、销毁或默认初始化一个类型,并且该类型是非法(分别地)复制、销毁或默认初始化,则该表达式是非法的。
如果程序包含具有非法复制或销毁的参数或返回值类型的函数类型说明符,则该程序是非法的。如果函数类型说明符由于此原因而将是非法的,但参数或返回值类型在翻译单元中的该位置不完整,则该程序是非法的,但不需要任何诊断。
如果 goto
或 switch
跳转到具有非平凡销毁类型的自动存储时长对象的范围,则它是非法的。
C 规定,如果在该位置没有该类型的对象,则访问左值通常是未定义的行为。实现通常对此很宽容,但非平凡类型通常需要更严格地执行。以下规则适用:
类型 T
在位置 L
的“静态子对象”是:
一个类型为
T
的对象,其范围从L
到L + sizeof(T)
。如果
T
是一个结构体类型,那么对于该结构体的每个字段f
,T
在位置L + offsetof(T, .f)
的静态子对象。如果
T
是数组类型E[N]
,那么对于每个满足0 <= i < N
的i
,E
在位置L + i * sizeof(E)
的静态子对象。
如果一个左值被转换为右值,那么所有类型为非平凡复制的静态子对象都会被访问。如果一个左值被赋值,或者如果一个自动存储持续时间的对象超出作用域,那么所有类型为非平凡销毁的静态子对象都会被访问。
如果一个初始化在某个位置初始化了该类型的对象,那么就会在该位置创建一个动态对象。如果内存被重新利用,那么该位置的动态对象将不再存在。内存被重新利用是指内存被释放或者在该位置创建了不同的动态对象,例如将不同的联合成员赋值。实现可能会提供有关构成创建或销毁动态对象的额外规则。
如果根据这些规则在一个不存在这种动态对象的位置访问对象,那么程序的行为将是不确定的。如果在该位置存在一个非平凡销毁的动态对象时,该位置的内存被重新利用,那么程序的行为将是不确定的。
基本原理
虽然这些规则比 C++ 的规则要粗粒度得多,但它们仍然足以表达各种类型的范围。表示某种所有权的类型通常在复制和销毁时是非平凡的,并且默认初始化是非平凡的或非法的。不表示所有权的类型可能仍然是非平凡复制的,因为它们对地址敏感;例如,相对引用。区分默认初始化允许类型对创建方式施加策略。
这些规则假设对左值的赋值始终是对现有对象的修改,而不是初始化。然后赋值是一个复合操作,其中旧值被读取并销毁(如果需要),然后新值被放到位。这些是值传播的自然语义,其中所有类型的基本操作都归结为复制和销毁,而其他所有操作只是对这些操作的优化。
在 C 中使用非平凡类型的最明显的弱点是,没有语言机制(类似于 C++ 的放置 new
和显式析构函数调用)来显式地创建和销毁对象。Clang 应该考虑为此目的添加内建函数,以及针对常见优化(如破坏性重新定位)添加内建函数。
正式 C 规则在非平凡所有权限定符上的应用¶
非平凡所有权限定的类型被认为在复制、销毁和默认初始化方面是非平凡的。
如果非平凡所有权限定类型的动态对象在一个位置存在,那么如果内存被填充为零模式,例如通过 calloc
或 bzero
。可以在上面所有情况下安全地访问此类对象,但其内存也可以安全地重新利用。将空指针赋值给 __weak
或 __strong
限定类型的左值将访问那里的动态对象(因此如果不存在此类对象可能会导致未定义的行为),但随后该对象的内存保证会被填充为零模式,因此可以根据需要进一步访问或重新利用。总之,程序可以通过确保非平凡所有权限定类型的动态分配内存被零初始化来安全地初始化该内存,并且可以通过将 nil
存储到之前在该内存中创建的任何 __strong
或 __weak
引用中来安全地取消初始化内存,然后再释放内存。
包含非平凡成员的结构体和联合体的 C/C++ 兼容性¶
包含非平凡成员的结构体和联合体在不同的语言模式下是兼容的(例如,在 Objective-C 和 Objective-C++ 之间,或者在 ARC 和非 ARC 模式之间),前提是满足以下条件
根据基线、非 ARC 规则(例如,C 结构体兼容性或 C++ 的 ODR),忽略所有权限定符后,类型必须是兼容的。此条件意味着字段之间存在一对一的对应关系。
请注意,具有基类的 Objective-C++ 类、用户提供的复制或移动构造函数或用户提供的析构函数永远不会与 Objective-C 类型兼容。
如果两个字段如上对应,并且至少有一个字段是所有权限定的,那么
字段必须具有相同的限定符,否则
一种类型必须是未限定的(因此在非 ARC 模式下声明),而另一种类型必须使用
__unsafe_unretained
或__strong
限定。
请注意,
__weak
字段必须始终声明为__weak
,因为需要将这些字段固定在内存中并使它们正确地向 Objective-C 运行时注册。非 ARC 模式仍然可以通过启用-fobjc-weak
来声明字段为__weak
。
这些兼容性规则允许在 ARC 中编写一个接受非平凡结构体类型参数的函数,并从非 ARC 中调用该函数,反之亦然。此规则的约定总是将存储在 __strong
字段中的对象的拥有权从调用者转移到被调用者,就像 ns_consumed
参数一样。因此,非 ARC 调用者必须确保此类字段被初始化为 +1 引用,而非 ARC 被调用者必须通过释放引用或适当地转移引用来平衡该 +1。
同样,返回非平凡结构体的函数可以在 ARC 中编写并在非 ARC 中调用,反之亦然。此规则的约定总是将存储在 __strong
字段中的对象的拥有权从被调用者转移到调用者,因此被调用者必须使用 +1 引用初始化此类字段,而调用者必须通过释放或转移它们来平衡该 +1。
类似的责任转移也会发生在 __weak
字段上,但由于双方都必须使用本地的 __weak
支持来确保调用约定兼容性,因此这种转移总是由编译器自动处理。
基本原理
在早期版本中,当非平凡所有权只允许在 Objective-C++ 中的字段上使用时,用于此类类的 ABI 是用于非平凡 C++ 类的普通 ABI,它间接传递参数并返回,并且不转移对参数的责任。在添加对 Objective-C 结构体的支持后,决定更改为当前 ABI,原因有三点
它允许 ARC/非 ARC 兼容性适用于只包含
__strong
引用,只要非 ARC 端小心地转移所有权。它避免了对 C ABI 宁愿在寄存器中传递的足够小的类型进行不必要的间接访问。
鉴于结构体参数必须以 +1 的形式生成以满足 C 的初始化局部参数变量的语义,将该副本的所有权转移到被调用者通常更有利于 ARC 优化,因为否则调用者将会有释放操作,这些操作更难与被调用者中的转移操作配对。
与现有的 Objective-C++ 结构体不兼容被认为是可以接受的代价,因为大多数 Objective-C++ 代码没有二进制兼容性要求。任何现有的代码不能接受这种兼容性破坏(它一定是 Objective-C++),应该通过声明一个空的(但不是默认的)析构函数来强制使用标准的 C++ ABI。
所有权推断¶
对象¶
如果一个对象被声明为具有可保留对象所有者类型,但没有显式所有权限定符,那么它的类型会隐式地调整为具有 __strong
限定符。
作为特殊情况,如果对象的基类型是 Class
(可能是协议限定的),那么类型会被调整为具有 __unsafe_unretained
限定符。
间接参数¶
如果函数或方法参数的类型为 T*
,其中 T
是一个所有权未限定的可保留对象指针类型,那么
如果
T
是const
限定的或Class
,那么它会被隐式地限定为__unsafe_unretained
;否则,它会被隐式地限定为
__autoreleasing
。
基本原理
__autoreleasing
主要存在于这种情况中,即 Cocoa 针对输出参数的约定。由于指向 const
的指针显然不是输出参数,因此我们使用更适合传递数组的类型。如果用户反而打算传入一个可变数组,那么推断 __autoreleasing
是错误的做法;这会导致以下有关写回的规则中的一些谨慎。
这种类型在其他任何地方写入都会因为需要所有权限定符的一般规则而导致格式错误。
如果参数的类型在模板模式中依赖于某个模板,并且只实例化为指向未限定的可保留对象指针类型的指针,那么此规则在 Objective-C++ 中不适用。此类代码仍然是格式错误的。
基本原理
此约定在模板代码中极不可能是有意的。
模板参数¶
如果模板类型参数的模板参数是可保留的对象所有者类型,并且没有显式的所有权限定符,则将其调整为具有__strong
限定符。此调整发生在模板参数是推断还是显式指定的情况下。
基本原理
__strong
是容器(例如,std::vector<id>
)的有用默认值,否则需要显式限定符。此外,未限定的可保留对象指针类型在模板中不太可能有用,因为它们通常需要在使用之前应用限定符。
方法族¶
Objective-C 方法可能属于一个方法族,它是由 Cocoa 约定赋予它的常规行为集。
如果方法满足以下条件,则它属于某个方法族:
它具有一个
objc_method_family
属性,将其置于该族中;或者如果没有,它没有一个
objc_method_family
属性将其置于不同的族中或没有族中,并且它的选择器属于相应的选择器族,并且
它的签名符合方法族的附加限制。
如果选择器忽略任何前导下划线,选择器的第一个组件完全由方法族名称组成,或者它以该名称开头,后面跟着一个非小写字母的字符,则该选择器属于某个选择器族。例如,_perform:with:
和performWith:
将属于perform
族(如果我们识别出一个),但是performing:with
则不会。
族及其附加限制是
alloc
方法必须返回可保留的对象指针类型。copy
方法必须返回可保留的对象指针类型。mutableCopy
方法必须返回可保留的对象指针类型。new
方法必须返回可保留的对象指针类型。init
方法必须是实例方法,并且必须返回一个 Objective-C 指针类型。此外,如果程序声明或包含对init
方法的调用,其返回类型既不是id
,也不是指向声明类(如果该方法是在类上声明的)或调用的静态接收器类型(如果该方法是在协议上声明的)的超类或子类的指针,则该程序格式错误。基本原理
有很多现有的方法具有
init
类选择器,但仍然不遵循init
约定。这些方法通常是意外的命名冲突,或者是在初始化过程中调用的辅助方法。由于init
方法的特殊保留/释放行为,如果这些方法不是init
方法,则非常重要不要将它们视为init
方法。我们认为根据返回类型和声明类之间的精确关系隐式地将这些方法定义为不在族中将过于微妙和脆弱。因此,我们确定了一些看似合法的返回类型,并将其他所有类型称为错误。这起到鼓励程序员不要意外地给方法命名为init
族中的名称的次要目的。请注意,具有
init
族选择器且返回非 Objective-C 类型(例如void
)的方法格式完全正确;它只是不在init
族中。
如果方法的声明、实现和覆盖没有都具有相同的方法族,则程序格式错误。
显式方法族控制¶
方法可以使用objc_method_family
属性进行注释,以精确控制它属于哪个方法族。如果@implementation
中的方法没有此属性,但相应的@interface
中声明的方法有,则该属性将复制到@implementation
中的声明中。该属性在 ARC 之外可用,并且可以使用预处理器查询__has_attribute(objc_method_family)
进行测试。
该属性的拼写为__attribute__((objc_method_family(
family )))
。如果 family 是none
,则该方法没有族,即使它根据其选择器和类型被认为具有族。否则,family 必须是alloc
、copy
、init
、mutableCopy
或new
之一,在这种情况下,该方法被认为属于相应的族,无论其选择器是什么。如果以这种方式显式添加到族中的方法不满足除选择器命名约定之外的族的要求,则会出错。
基本原理
本文档中编纂的规则描述了 Objective-C 的标准约定。但是,由于这些约定以前没有由一个不容情机械系统强制执行,因此它们只是不完美地保持,尤其是在它们没有始终精确定义的情况下。虽然可以使用ns_returns_retained
等属性来定义低级所有权语义,但此属性允许用户传达语义意图,这对 ARC(例如,它以特殊方式处理对init
的调用)和静态分析器都很有用。
方法族的语义¶
方法在方法族中的成员身份可能暗示其参数和返回类型的非标准语义。
属于alloc
、copy
、mutableCopy
和new
族的方法——也就是说,所有当前定义的族中除了init
之外的方法——隐式地返回保留的对象,就好像它们使用ns_returns_retained
属性进行注释一样。这可以通过使用ns_returns_autoreleased
或ns_returns_not_retained
属性对方法进行注释来覆盖。
属性也遵循与方法相同的命名规则。这意味着那些属于alloc
、copy
、mutableCopy
和new
族中的属性提供了对保留的对象的访问。这可以通过使用ns_returns_not_retained
属性对属性进行注释来覆盖。
的语义init
¶
属于init
族的方法隐式地消耗它们的self
参数,并返回保留的对象。这两种属性都不能通过属性来改变。
对具有self
(可能已加括号或已强制转换)或super
作为接收器的init
方法的调用被称为委托 init 调用。除init
方法(以及这些方法中的块除外)之外,不允许执行委托 init 调用。
作为对通常规则的例外,变量self
在init
方法中是可变的,并且对__strong
变量具有通常的语义。但是,如果init
方法尝试在完成委托 init 调用后使用self
的先前值,则其行为未定义,并且程序格式错误,不需要诊断。通常情况下,但并非必需,init
方法会返回self
。
对于程序而言,在同一个对象上对init
方法进行两次或多次调用会导致行为未定义,但每次init
方法调用最多可以执行一次委托 init 调用。
优化¶
在本节中,术语 函数 将用于指代任何结构化的代码单元,无论是 C 函数、Objective-C 方法还是块。
本规范描述了 ARC 在程序执行过程中的特定点对可保留对象指针执行特定的 retain
和 release
操作。这些操作构成了程序计算历史的非连续子序列。对于特定可保留对象指针,特定函数执行直接负责的此序列的部分是该对象指针的 正式本地保留历史。相应的实际执行序列是 动态本地保留历史。
但是,在某些情况下,ARC 被允许以可能改变整体计算历史的方式重新排序和消除操作,超出 C/C++ 的一般“as if”规则和 限制 对 retain
和 release
实现的限制。
基本原理
具体而言,ARC 有时被允许以可能导致对象在预期之前被释放的方式优化 release
操作。如果没有这个,几乎不可能消除任何 retain
/release
对。例如,考虑以下代码
id x = _ivar;
[x foo];
如果我们无论如何都不被允许缩短 x
中对象的生存期,那么我们将无法消除这个 retain 和 release,除非我们能够证明消息发送不会修改 _ivar
(或释放 self
)。由于消息发送对于优化器是不透明的,因此这是不可能的,因此 ARC 的行动几乎完全受到限制。
ARC 对包含未定义行为的计算历史的执行不作任何保证。特别是,ARC 对存在竞态条件的情况不作任何保证。
ARC 可能假设它接收或生成的任何可保留对象指针从该点开始到某个点都是瞬时有效的,根据宿主语言的并发模型,该点发生在指针生成之后并且发生在该对象释放之前(可能通过别名指针或由于销毁了不同的对象而间接导致)。
基本原理
在存在竞态条件的情况下尝试保证正确性几乎毫无意义。ARC 没有堆栈扫描垃圾收集器,保证每个加载和存储操作的原子性将是禁止性的,并且会阻止大量的优化。
ARC 可能假设非 ARC 代码参与了合理的平衡行为,并且不依赖于确切或最小的保留计数值,除非由 __strong
对象不变式或 +1 转移约定保证。例如,如果一个对象被证明是双重保留和双重释放,ARC 可以消除内部的 retain 和 release;它不需要防范执行不平衡释放后紧随其后的“平衡”保留的代码。
对象生存期¶
ARC 可能不允许在计算历史中的时间 T
释放可保留对象 X
,如果
X
是存储在具有 精确生存期语义 的__strong
对象S
中的值,或者X
是存储在具有不精确生存期语义的__strong
对象S
中的值,并且在T
之后但下一次存储到S
之前,计算历史包含从S
加载的值,并且在某种程度上依赖于加载的值,或者X
是一个描述为在当前完整表达式结束时释放的值,并且在T
之后但在完整表达式结束之前,计算历史依赖于该值。
基本原理
第二条规则的目的是说明在普通 __strong
局部变量中保存的对象可以在变量不再使用时立即释放:变量完全停止使用或变量中存储了新值。
第三条规则的目的是说明返回值可以在它们被使用后被释放。
如果计算历史
执行与
P
的指针比较,从
P
加载,存储到
P
,依赖于通过指针运算从
P
派生的指针值Q
(包括实例变量或字段访问),或者依赖于从
P
加载的指针值Q
。
依赖性仅适用于直接或间接从特定表达式结果派生的值,并且不会仅仅因为单独的指针值动态地别名 P
而出现。此外,这种依赖性不会由存储到对象的的值携带。
基本原理
对依赖性的限制旨在使优化器仅具有关于程序的不完整信息的情况下,这种分析成为可能。本质上,依赖性被传递到指针的“明显”用途。仅仅将指针参数传递给函数本身不会导致依赖性,但由于通常优化器将无法证明该函数不依赖于该参数,因此它将被迫保守地假设它依赖于该参数。
依赖性会传播到从指针加载的值,因为这些值可能会因释放对象而失效。例如,给定代码 __strong id x = p->ivar;
,ARC 必须不能将 p
的释放移动到加载 p->ivar
和保留该值以存储到 x
之间。
依赖性不会通过存储依赖指针值而传播,因为这样做将允许依赖性超出产生原始值的完整表达式的范围。例如,实例变量的地址可以被写入某个全局位置,然后可以在局部变量的生存期内自由访问,或者函数可以返回对象的内部指针并将它存储到局部变量中。这些情况可能难以推理,因此基本上会阻止任何基于不精确生存期的优化。这些情况也不常见,因此如果有人确实想依赖它们,要求使用精确生存期注释是合理的。
依赖性会通过指针类型的返回值传播。对这条规则的迫切需要来源是返回未自动释放结果的属性访问器;调用函数必须有机会在 ARC 释放原始指针之前对该值进行操作,例如保留它。再次注意,但是,依赖性不会在存储后持续存在,因此 ARC 不保证返回值在完整表达式结束后继续有效。
没有对象生存期扩展¶
如果在程序的正式计算历史中,对象 X
在可观察的副作用发生时已经被释放,那么 ARC 必须导致 X
最晚在该副作用发生时被释放,除非受到对象销毁重新排序的影响。
基本原理
这条规则的目的是禁止 ARC 观察性地扩展可保留对象的生存期,除非在本规范中规定。与限制释放转换的规则一起,这条规则要求 ARC 仅成对消除 retain 和 release。
ARC 重新排序对象销毁的能力对于它能够进行任何优化至关重要,这与它必须保留减少对象生存期的能力的理由基本相同。不幸的是,虽然对象销毁具有任意副作用通常是不好的风格,但这当然是有可能的。因此存在这个免责声明。
精确生存期语义¶
通常,ARC 保持一个不变式,即存储在 __strong
对象中的可保留对象指针将在对象的整个正式生存期内被保留。受此不变式约束的对象具有 精确生存期语义。
默认情况下,具有自动存储期的局部变量没有精确的生存期语义。此类对象只是持有可保留对象指针类型值的强引用,这些值仍然完全受局部控制下值的优化影响。
基本原理
严格地应用这些精确生存期语义将是禁止性的。许多理论上可能缩短对象生存期的有用优化将变得不可能。本质上,它承诺得太多。
具有可保留对象所有者类型和自动存储期的局部变量可以使用 objc_precise_lifetime
属性进行注释,以指示它应该被视为具有精确生存期语义的对象。
基本原理
尽管如此,有时仍然有必要能够强制在精确的时间释放对象,即使该对象似乎没有被使用。这可能并不常见,因此显式请求这些语义的语法负担不会很重,甚至可能使代码更清晰。
其他¶
特殊方法¶
内存管理方法¶
如果程序包含以下任何选择器的任何方法定义、消息发送或 @selector
表达式,则该程序格式错误。
autorelease
release
retain
retainCount
基本原理
retainCount
被禁止,因为 ARC 使其失去了一致的语义。在权衡了处理消息发送的三种选项后,其他方法也被禁止了
尊重它们如果程序员天真或意外地尝试将为手动保留/释放代码编写的代码合并到 ARC 程序中,则效果会非常糟糕。最好的情况是,这种代码的工作量是必要的的两倍;然而,通常情况下,ARC 和显式代码都会试图平衡相同的保留,从而导致崩溃。代价是失去了执行“未根植”保留的能力,即与对象图中的强引用不逻辑对应的保留。
忽略它们会严重违反用户对其代码的期望。虽然它确实会使为 ARC 和非 ARC 同时开发代码变得更容易,但除了某些库开发人员之外,几乎没有理由这样做。ARC 和非 ARC 翻译单元共享一个执行模型,可以无缝地互操作。在一个翻译单元中,忠实地以非 ARC 模式维护其代码的开发人员正在承受 ARC 的所有限制,而没有得到任何好处,而没有测试非 ARC 模式的开发人员如果尝试返回到非 ARC 模式,很可能会感到不愉快。
禁止它们有使其将现有代码迁移到 ARC 变得非常尴尬的缺点。鉴于 ARC 中的其他更改和限制,对这个问题的最佳答案是提供一个专门的工具来帮助用户进行这种迁移。
实施这些方法被禁止,因为它们对于 ARC 的语义过于重要;如果 ARC 执行一个或两个短暂的额外保留,那么在手动引用计数下可行的大多数技巧都会出现行为异常。如果绝对需要,仍然可以在非 ARC 代码中实现它们,例如在类别中;这些实现必须遵守 语义,这些语义在本文件中其他地方阐述。
dealloc
¶
如果程序包含选择器 dealloc
的消息发送或 @selector
表达式,则该程序格式错误。
基本原理
没有正当理由直接调用 dealloc
。
类可以为名为 dealloc
的实例方法提供方法定义。在对对象进行最后 release
之后,但在它被释放或其任何实例变量被销毁之前,将调用此方法。当方法返回时,将自动调用超类的 dealloc
实现。
基本原理
即使 ARC 自动销毁实例变量,仍然有正当理由编写 dealloc
方法,例如释放不可保留的资源。在这样的方法中不调用 [super dealloc]
几乎总是错误。有时,对象只是试图阻止自己被销毁,但 dealloc
对对象来说实在是太迟了,无法提出这样的异议。更合法的是,对象可能已被池分配,不应使用 free
释放;目前,这只能通过 ARC 之外的 dealloc
实现来支持。这种实现必须非常小心地完成 NSObject
的 dealloc
会做的所有其他工作,这超出了本文档描述的范围。
ARC 编译类的实例变量将在某个时间点被销毁,该时间点是在控制进入类根类的 dealloc
方法之后。实例变量销毁的顺序是不确定的,无论是在单个类中,还是在子类和超类之间。
基本原理
销毁实例变量的传统非 ARC 模式是在调用 [super dealloc]
之前立即销毁它们。不幸的是,来自超类的消息发送完全有可能到达子类中的方法,而这些方法很可能读写这些实例变量。通常不鼓励从 dealloc 进行这样的消息发送,因为子类可能依赖于在 dealloc
期间被破坏的其他不变式,但它并不像我们认为可以将其称为未定义行为那样不可避免地危险。因此,我们选择将销毁实例变量推迟到明确禁止消息发送的点:根类的释放例程接管的点。
在大多数代码中,差异不可观察。但是,如果实例变量对一个对象的强引用,而该对象的释放将触发必须与超类销毁严格排序的副作用,则可以观察到它。这种代码违反了语义上重要的行为应该是显式的设计原则。一个简单的解决办法是在 dealloc
期间手动清除实例变量;更全面的解决方案是将语义上重要的副作用从 dealloc
中移出,并移到一个单独的拆卸阶段,该阶段可以依赖于使用格式良好的对象。
@autoreleasepool
¶
为了简化自动释放池的使用,并将它们置于编译器的控制之下,Objective-C 中提供了一种新的语句。它写成 @autoreleasepool
后跟一个复合语句,即由花括号分隔的新范围。进入此块时,会捕获自动释放池的当前状态。当块以正常方式退出时,无论是通过贯穿还是通过定向控制流(例如 return
或 break
),自动释放池都会恢复到保存的状态,释放其中的所有对象。当块以异常退出时,池不会被清空。
@autoreleasepool
可以在非 ARC 翻译单元中使用,具有相同的语义。
如果程序引用 NSAutoreleasePool
类,则该程序格式错误。
基本原理
自动释放池对于编译器来说显然很重要,但期望编译器准确地推断两次调用之间的控制依赖关系则太过分。使用手动 API 时,也很容易意外地忘记清空自动释放池,这会导致进程的高水位线明显膨胀。引入一个新范围是不幸的,但对于与语言其他部分的合理交互来说,这是基本要求。在解开期间不清空池显然是 Objective-C 异常实现所要求的。
外部保留的变量¶
在某些情况下,具有强所有权的变量被实现视为外部保留的。这意味着该变量在其他地方被保留,因此实现可以省略保留和释放其值。这样的变量隐式地是 const
,以确保安全。与 __unsafe_unretained
不同,外部保留的变量在初始化和销毁之外仍然表现为强变量。例如,当外部保留的变量在块中捕获时,变量的值在块捕获和销毁时会被保留和释放。它还会影响 C++ 功能,例如 lambda 捕获、decltype
和模板参数推断。
隐式地,实现假设 非 init 方法中的 self 参数 和 for-in 循环中的变量 是外部保留的。
也可以使用 objc_externally_retained
属性选择使用外部保留的语义。此属性可以应用于强局部变量、函数、方法或块。
@class WobbleAmount;
@interface Widget : NSObject
-(void)wobble:(WobbleAmount *)amount;
@end
@implementation Widget
-(void)wobble:(WobbleAmount *)amount
__attribute__((objc_externally_retained)) {
// 'amount' and 'alias' aren't retained on entry, nor released on exit.
__attribute__((objc_externally_retained)) WobbleAmount *alias = amount;
}
@end
使用此属性注释函数会使具有强保留对象指针类型的每个参数都成为外部保留的,除非该变量明确使用 __strong
进行限定。例如,下面的 first_param
是外部保留的(因此是 const
),但 second_param
不是
__attribute__((objc_externally_retained))
void f(NSArray *first_param, __strong NSArray *second_param) {
// ...
}
您可以使用 __has_attribute
测试您的编译器是否支持 objc_externally_retained
。
#if __has_attribute(objc_externally_retained)
// Use externally retained...
#endif
self
¶
非 init Objective-C 方法的 self
参数变量被实现视为 外部保留的。导致对象在向该对象发送消息期间被释放是未定义的行为,或者至少是危险的。在 init 方法中,self
遵循 :ref:init family rules <arc.family.semantics.init>
。
基本原理
在所有方法中保留 self
的成本被发现是过高的,因为它往往会在调用之间保持活动状态,阻止优化器证明保留和释放是不必要的——这是有充分理由的,因为理论上很有可能在对象执行期间导致对象被释放而没有这种保留和释放。由于实际这样做非常罕见,即使是无意的,并且由于程序员没有自然的方式来删除这种保留/释放对(就像通过将变量设置为 objc_externally_retained
或使用 __unsafe_unretained
限定它来删除其他参数一样),我们选择做出这种优化假设,并将一定程度的风险转移给用户。
快速枚举迭代变量¶
如果在 Objective-C 快速枚举循环的条件中声明了一个变量,并且该变量没有显式的所有权限定符,那么它将被隐式地外部保留,这样在枚举过程中遇到的对象实际上不会被保留和释放。
基本原理
这是一种优化,因为快速枚举循环承诺在枚举期间保留对象,并且集合本身不能同步修改。可以通过用 __strong
显式限定变量来覆盖它,这将使变量再次可变,并导致循环保留它遇到的对象。
块¶
在计算块文字表达式时创建的隐式 const
捕获变量具有与其捕获的局部变量相同的所有权语义。捕获是通过从捕获的变量中读取并使用该值初始化捕获的变量来执行的;当块文字被销毁时,捕获的变量也被销毁,即在封闭作用域结束时。
推断规则同样适用于 __block
变量,这与非 ARC 的语义不同,在非 ARC 中,__block
变量在捕获期间不会隐式保留。
__block
类型为可保留对象所有者的变量通过使用堆副本初始化的结果从堆栈副本中移动来从堆栈中移出。
除了作为初始化 __strong
参数变量或读取 __weak
变量的一部分而执行的保留之外,只要这些语义需要保留块指针类型的值,它就会产生 Block_copy
的效果。当优化器看到结果只用作调用参数时,它可能会删除这些副本。
当块指针类型转换为非块指针类型(如 id
)时,将调用 Block_copy
。这是必要的,因为当非块指针逃逸时,在堆栈上分配的块不会被复制到堆中。当块指针作为可变参数传递给函数时,它将被隐式转换为 id
。
异常¶
在 Objective C 中,默认情况下,ARC 对正常释放来说不是异常安全的。
当
__strong
变量的作用域被异常异常终止时,它不会结束__strong
变量的生命周期。它不会执行在完整表达式结束时发生的释放,如果该完整表达式抛出异常。
程序可以使用选项 -fobjc-arc-exceptions
进行编译以启用这些选项,或者使用选项 -fno-objc-arc-exceptions
显式禁用它们,最后一个这样的参数将“胜出”。
基本原理
标准 Cocoa 约定是异常表示程序员错误,并且不打算从中恢复。默认情况下使代码异常安全将在通常不关心异常安全的代码上造成严重的运行时和代码大小惩罚。因此,ARC 生成的代码在异常时默认情况下会泄漏,如果进程将立即终止,这很好。关心从异常中恢复的程序应该启用该选项。
在 Objective-C++ 中,-fobjc-arc-exceptions
默认启用。
基本原理
C++ 已经引入了 ARC 引入的那种普遍的异常清理代码。尚未禁用异常的 C++ 程序员更有可能实际需要异常安全性。
ARC 在异常终止其作用域时会结束 __weak
对象的生命周期,除非在编译器中禁用了异常。
基本原理
局部 __weak
对象未被销毁的后果很可能是 Objective-C 运行时的损坏,因此我们希望在这里更安全。当然,如果程序试图从异常中恢复,潜在的大量泄漏与这种损坏一样有可能使进程崩溃。
内部指针¶
返回不可保留指针的 Objective-C 方法可以用 objc_returns_inner_pointer
属性进行注释,以指示它返回指向对象的内部数据的句柄,并且如果对象被销毁,则该引用将失效。当向对象发送此类消息时,对象的生存期将延长,直到至少满足以下条件中的最早者
在调用函数或
自动释放池恢复到以前的状态中,返回的指针或任何从它派生的指针的最后一次使用。
基本原理
理由:并非所有内存和资源都是用引用计数管理的;对象以自己的私有方式管理私有资源很常见。通常情况下,这些资源完全封装在对象中,但一些类为其用户提供直接访问以提高效率。如果 ARC 不了解返回此类“内部”指针的方法,其优化会导致拥有对象过早地被回收。此属性通知 ARC 它必须谨慎行事。
扩展规则在某种程度上是有意地模糊不清。自动释放池限制的存在是为了允许一个简单的实现简单地保留和自动释放接收器。另一个限制允许一定程度的优化。短语“派生自”旨在涵盖指针转换(如强制转换和算术)以及从这些派生指针加载的结果;此外,它适用于此类派生是直接在调用代码中还是由其他实用程序代码(例如 C 库例程 strchr
)应用。但是,实现永远不需要考虑在从返回内部指针的方法调用的代码返回后使用。
作为例外,如果接收器直接从具有精确生命周期语义的 __strong
对象加载,则不需要扩展。
基本原理
隐式自动释放会带来显著增加内存使用量的风险,因此为用户提供避免这些自动释放的方法非常重要。将此与精确生命周期语义绑定是理想的,因为对于局部变量来说,这需要一个非常明确的注释,这使得 ARC 可以放心地信任用户。
C 可保留指针类型¶
如果类型是指向(可能限定的)void
的指针或指向(可能限定的)struct
或 class
类型的指针,那么它就是一个C 可保留指针类型。
基本原理
ARC 不管理 CoreFoundation 类型的指针(或任何与 Objective-C 互操作以进行保留/释放操作的相关可保留 C 指针系列)。事实上,ARC 甚至不知道如何将这些类型与任意 C 指针类型区分开来。这个概念的目的是过滤掉一些明显非对象类型,同时为以后收紧留下一个钩子,如果找到了全面标记 CF 类型的办法。
C 可保留指针接口的审计¶
[自 Apple 4.0,LLVM 3.1 起]
可以用 cf_audited_transfer
属性标记 C 函数,以表示除了使用属性标记的函数之外,它都遵循其名称的 C 函数的参数(消耗 vs. 非消耗)和返回值(保留 vs. 非保留)约定,即
除非使用
cf_consumed
属性标记,否则 C 可保留指针类型的参数假定不会被消耗,并且C 可保留指针类型的结果假定不会被保留返回,除非函数被标记为
cf_returns_retained
,或者它遵循创建/复制命名约定,并且没有被标记为cf_returns_not_retained
。
如果函数的名称包含以下子字符串,则它遵循创建/复制命名约定
要么是“Create”或“Copy”,后面不接小写字母,要么是
要么是“create”或“copy”,后面不接小写字母,前面也不接任何字母,无论是大写字母还是小写字母。
第二个属性 cf_unknown_transfer
表示函数的传输语义无法使用任何这些注释准确捕获。如果程序使用 cf_audited_transfer
和 cf_unknown_transfer
同时注释同一个函数,则该程序格式错误。
提供了一个预处理指令来方便大量注释接口
#pragma clang arc_cf_code_audited begin
...
#pragma clang arc_cf_code_audited end
在此预处理指令的范围内声明的所有 C 函数都被视为使用 cf_audited_transfer
属性进行注释,除非它们具有 cf_unknown_transfer
属性。预处理指令在所有语言模式下都被接受。如果程序试图在该预处理指令的范围内更改文件(无论是包含文件还是结束当前文件),则该程序格式错误。
可以使用 __has_feature(arc_cf_code_audited)
测试本节中的所有功能。
基本原理
ARC 编程中一个重大不便之处是必须与基于 C 可保留指针的 API 进行交互。这些功能旨在使 API 作者能够快速审核和注释其接口,从而提高静态分析器和 ARC 等工具的保真度。预处理指令的单文件限制旨在消除意外注释其他头文件的接口的风险。
运行时支持¶
本节描述 ARC 运行时与 ARC 编译器生成的代码之间的交互。这不是 ARC 语言规范的一部分;相反,它实际上是一个特定于语言的 ABI 补充,类似于 C++ 的“Itanium”通用 ABI。
所有权限定符不会更改对象的存储要求,除了如果 __weak
对象的对其方式不足以容纳类型为 id
的对象,则行为未定义。其他限定符可以在显式地未对齐的内存上使用。
运行时跟踪包含非空值的 __weak
对象。直接修改运行时正在跟踪的 __weak
对象的行为是未定义的,除非通过 objc_storeWeak、objc_destroyWeak 或 objc_moveWeak 调用。
运行时必须提供一些新的入口点,编译器可能会发出这些入口点,这些入口点将在本节的剩余部分中描述。
基本原理
其中一些函数在语义上等效于发送消息;我们发出对 C 函数的调用,因为
执行此操作的机器代码要小得多,
在 ARC 优化器中更容易识别 C 函数,以及
足够复杂的运行时可能能够在常见情况下避免发送消息。
这些函数中的其他一些是“融合”操作,它们可以用其他操作完全描述。我们主要使用融合操作作为代码大小优化,尽管在某些情况下,在运行时避免冗余操作也具有真正的潜力。
id objc_autorelease(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它将对象添加到最里面的自动释放池,就像该对象已收到 autorelease
消息一样。
始终返回 value
。
void objc_autoreleasePoolPop(void *pool);
¶
前提条件: pool
是当前线程之前对 objc_autoreleasePoolPush 的调用的结果,其中 pool
或任何封闭池以前都没有被弹出。
释放添加到给定自动释放池及其封闭的所有自动释放池中的所有对象,然后将当前自动释放池设置为直接封闭 pool
的池。
void *objc_autoreleasePoolPush(void);
¶
创建一个新的自动释放池,该池被当前池封闭,使其成为当前池,并返回一个不透明的“句柄”指向它。
基本原理
虽然该接口被描述为池的显式层次结构,但这些规则允许实现仅维护一个对象堆栈,使用堆栈深度作为不透明池句柄。
id objc_autoreleaseReturnValue(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它会尽力将对对象保留计数的拥有权移交给对 objc_retainAutoreleasedReturnValue(或 objc_unsafeClaimAutoreleasedReturnValue)的调用,该调用用于在封闭的调用帧中针对相同对象进行调用。如果这不可行,则该对象将如上所述被自动释放。
始终返回 value
。
void objc_copyWeak(id *dest, id *src);
¶
前提条件: src
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。 dest
是一个有效的指针,尚未注册为 __weak
对象。
dest
被初始化为等效于 src
,可能将其注册到运行时。等效于以下代码
void objc_copyWeak(id *dest, id *src) {
objc_release(objc_initWeak(dest, objc_loadWeakRetained(src)));
}
必须对对 src
上的 objc_storeWeak
的调用进行原子操作。
void objc_destroyWeak(id *object);
¶
前提条件: object
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。
object
被取消注册为弱对象(如果有)。object
的当前值未指定;否则,等效于以下代码
void objc_destroyWeak(id *object) {
objc_storeWeak(object, nil);
}
不需要对对 object
上的 objc_storeWeak
的调用进行原子操作。
id objc_initWeak(id *object, id value);
¶
前提条件: object
是一个有效的指针,尚未注册为 __weak
对象。 value
为空或指向有效对象的指针。
如果 value
是一个空指针,或者它指向的对象已开始释放,则 object
将被初始化为零。否则,object
将被注册为指向 value
的 __weak
对象。等效于以下代码
id objc_initWeak(id *object, id value) {
*object = nil;
return objc_storeWeak(object, value);
}
返回调用后的 object
的值。
不需要对对 object
上的 objc_storeWeak
的调用进行原子操作。
id objc_loadWeak(id *object);
¶
前提条件: object
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。
如果 object
注册为 __weak
对象,并且存储到 object
中的最后一个值尚未被释放或开始释放,则保留并自动释放该值并将其返回。否则返回空。等效于以下代码
id objc_loadWeak(id *object) {
return objc_autorelease(objc_loadWeakRetained(object));
}
必须对对 object
上的 objc_storeWeak
的调用进行原子操作。
基本原理
在没有保留的情况下,加载弱引用本身容易出现竞争条件。
id objc_loadWeakRetained(id *object);
¶
前提条件: object
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。
如果 object
注册为 __weak
对象,并且存储到 object
中的最后一个值尚未被释放或开始释放,则保留该值并将其返回。否则返回空。
必须对对 object
上的 objc_storeWeak
的调用进行原子操作。
void objc_moveWeak(id *dest, id *src);
¶
前提条件: src
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。 dest
是一个有效的指针,尚未注册为 __weak
对象。
dest
被初始化为等效于 src
,可能将其注册到运行时。 src
随后可以保留其原始状态,在这种情况下,此调用等效于 objc_copyWeak,或者它可以保留为空。
必须对对 src
上的 objc_storeWeak
的调用进行原子操作。
void objc_release(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它将执行释放操作,就像该对象已收到 release
消息一样。
id objc_retain(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它将执行保留操作,就像该对象已收到 retain
消息一样。
始终返回 value
。
id objc_retainAutorelease(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它将执行保留操作,然后执行自动释放操作。等效于以下代码
id objc_retainAutorelease(id value) {
return objc_autorelease(objc_retain(value));
}
始终返回 value
。
id objc_retainAutoreleaseReturnValue(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它将执行保留操作,然后执行 objc_autoreleaseReturnValue 中描述的操作。等效于以下代码
id objc_retainAutoreleaseReturnValue(id value) {
return objc_autoreleaseReturnValue(objc_retain(value));
}
始终返回 value
。
id objc_retainAutoreleasedReturnValue(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用不会有任何影响。否则,它尝试接受在最近调用的函数或其尾部调用的函数中对 value
上的 objc_autoreleaseReturnValue 的调用的保留计数移交。如果失败,它将执行保留操作,就像 objc_retain 一样。
始终返回 value
。
id objc_retainBlock(id value);
¶
前提条件: value
为空或指向有效块对象的指针。
如果 value
为空,则此调用无效。否则,如果 value
指向的块仍在堆栈上,则将其复制到堆中,并返回副本的地址。否则,将对该块执行保留操作,就像它已发送 retain
消息一样。
void objc_storeStrong(id *object, id value);
¶
前提条件: object
是指向 __strong
对象的有效指针,该对象已充分对齐以用于指针。 value
为空或指向有效对象的指针。
执行将非块类型 [*] 的 __strong
对象分配给的完整序列。等效于以下代码
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}
id objc_storeWeak(id *object, id value);
¶
前提条件: object
是一个有效的指针,它要么包含一个空指针,要么已注册为 __weak
对象。 value
为空或指向有效对象的指针。
如果 value
是一个空指针或它指向的对象已开始释放,则 object
被分配为空,并取消注册为 __weak
对象。否则,将 object
注册为 __weak
对象,或更新其注册以指向 value
。
返回调用后的 object
的值。
id objc_unsafeClaimAutoreleasedReturnValue(id value);
¶
前提条件: value
为空或指向有效对象的指针。
如果 value
为空,则此调用无效。否则,它会尝试从最近调用的函数或其尾调用(以类似于 objc_retainAutoreleasedReturnValue 的方式)中对 value
调用 objc_autoreleaseReturnValue 来接收保留计数的传递。如果成功,它将执行与 objc_release 完全相同的释放操作。如果传递失败,则此调用无效。
始终返回 value
。