为什么初始化此 swift 对象会产生 2 而不是 1 的 ARC?

Why does initializing this swift object produce an ARC of 2 instead of 1?

我 运行 在我的项目中遇到了问题,我意识到没有按需要释放对象。我决定测试对象的 ARC,在初始化之后它是 2。在下面这个简单的例子中也是如此。为什么 ARC 2 而不是 1?

import SpriteKit

class LevelBuilder:SKNode{
    var testNode:SKSpriteNode?
    init(with color:SKColor){
        super.init()
        self.testNode = SKSpriteNode(color: color, size: CGSize(width: 2, height: 2))
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

let test = LevelBuilder(with: .red)
print("ARC: \(CFGetRetainCount(test))")

它打印 ARC: 2

可能是因为您初始化了 testNode,它是一个 SKSpriteNode,它也可能在底层引用 SKNode。因此,您的第一个引用来自 LevelBuilder class,第二个引用来自 testNode。

不存在 "the ARC of the object." 你考虑的是保留计数。很难想象还有比保留计数更无意义的数字。它要么是零(在这种情况下对象消失了,所以你永远不会看到它),要么是 "not zero."

保留计数是对对象提出的所有权声明的数量。系统的任何部分都可以随时自由地提出所有权声明。系统的任何部分都可以随时取消其所有权声明。有一个叫做自动释放池的东西,它持有所有权声明并会自动释放这些声明 "at some point in the future." 一个对象在任何给定时间拥有多个自动释放保留是完全正常的。这将增加保留计数,但保留计数稍后会下降。

如果保留计数在 MRC 下毫无意义(它们确实如此),那么它们在 ARC 下就完全是疯子了,编译器可以随时自由地优化它们,它可以证明这无关紧要,并且经常注入额外的保留当它不能证明不需要它们时(特别是与函数调用相关)。所以实际值就更没有意义了。例如,在 ARC 下,test 在调用 CFGetRetainCount 之前附加一个额外的保留是完全合适的,只是为了确保 test 不会被释放得太快。

如果你有内存管理问题,你想使用像内存图调试器这样的工具(并且只是寻找强引用和特别是强循环)。检查保留计数只会骗你。

在您的特定情况下,我们可以使用 swiftc -emit-sil 对其进行一些探索,从我们进行字符串插值的那一点开始(即最后一行中的 ""):

// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%34 = function_ref @$SSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %35
%35 = apply %34(%30, %31, %32, %33) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %37
%36 = alloc_stack $String                       // users: %39, %37, %41
store %35 to %36 : $*String                     // id: %37
// function_ref specialized String.init<A>(stringInterpolationSegment:)
%38 = function_ref @$SSS26stringInterpolationSegmentSSx_tcs23CustomStringConvertibleRzs20TextOutputStreamableRzlufCSS_Tg5 : $@convention(method) (@owned String, @thin String.Type) -> @owned String // user: %40
%39 = load %36 : $*String                       // user: %40
%40 = apply %38(%39, %29) : $@convention(method) (@owned String, @thin String.Type) -> @owned String // user: %42
dealloc_stack %36 : $*String                    // id: %41
store %40 to %28 : $*String                     // id: %42
%43 = integer_literal $Builtin.Word, 1          // user: %44
%44 = index_addr %28 : $*String, %43 : $Builtin.Word // user: %58
%45 = metatype $@thin String.Type               // user: %56
%46 = load %3 : $*LevelBuilder                  // users: %48, %47

=========
strong_retain %46 : $LevelBuilder               // id: %47
%48 = init_existential_ref %46 : $LevelBuilder : $LevelBuilder, $AnyObject // user: %49
%49 = enum $Optional<AnyObject>, #Optional.some!enumelt.1, %48 : $AnyObject // users: %52, %51
// function_ref CFGetRetainCount
%50 = function_ref @CFGetRetainCount : $@convention(c) (Optional<AnyObject>) -> Int // user: %51
%51 = apply %50(%49) : $@convention(c) (Optional<AnyObject>) -> Int // user: %54
release_value %49 : $Optional<AnyObject>        // id: %52
=========

我用 === 行标记了重要部分。 test 上有很强的保留。然后将其包装到 AnyObject? 包装器中以传递给 C 函数 (GetRetainCount)。该函数被调用。然后释放 Optional 的值(即 test)。所以当你调用 GetRetainCount.

时你应该期待一个额外的保留

但是如果你用 -O 重新编译它,你会注意到没有 strong_retain 指令。 ARC 认为额外的保留实际上不是必需的,因此将其删除。所以这表明通过优化保留计数将为 1。我想知道这是不是真的:

$ swiftc main.swift
$ ./main
ARC: 2
$ swiftc -O main.swift
$ ./main
ARC: 1

果然.