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

时间:2018-10-01 22:55:02

标签: ios swift sprite-kit

我在项目中遇到问题,我意识到没有按需要重新分配对象。我决定测试对象的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

2 个答案:

答案 0 :(得分:3)

没有诸如“对象的ARC”之类的东西。您正在考虑的是保留计数。很难想象数字比保留数更无意义。它要么为零(在这种情况下该对象消失了,所以您将永远不会看到它),要么为“非零”。

保留计数是已放置在对象上的所有权声明的数量。系统的任何部分都可以随时随时提出所有权要求。系统的任何部分都可以随时删除其所有权声明。有一个叫做自动释放池的整体,它拥有所有权声明,并将在“将来某个时候”自动释放这些声明。在任何给定时间在对象上保留多个自动释放是完全正常的。这样会增加保留数,但保留数稍后会下降。

如果保留计数在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

果然。

答案 1 :(得分:0)

可能是因为您初始化了一个testtestNode,它是一个SKSpriteNode,它也可能在后台引用了SKNode。因此,您可以从LevelBuilder类中获得第一个引用,而第二个则来自testNode。