处理多个单例类时代码冻结

时间:2017-10-16 09:51:30

标签: ios swift swift3

我有两个单身人士课程。我在Playground中实现了代码。代码如下

//: Playground - noun: a place where people can play

import UIKit
class A {
    private init() {
        print("1")
        setB()
        print("2")
    }

    static let a = A()

    func setB() {
        _ = B.b
    }
}

class B {
    private init() {
        setA()
    }

    static let b = B()

    func setA() {
        print("3")
        _ = A.a // Doesn't execute beyond this
        print("4")
    }
}

_ = A.a

我在外面创建Class A个对象,然后创建Class B的对象。由于A是单例,因此已经创建了对象。然后在B的内部构造函数中,我正在访问A的对象。但代码并没有超出该行。上述代码的输出是

1
3

任何人都可以帮我理解代码有什么问题吗?我想知道为什么它会停止执行。没有语法错误/警告。

1 个答案:

答案 0 :(得分:3)

  

由于A是单身,因此已经创建了对象。

不正确。默认情况下,静态属性是惰性的。这意味着A.aB.b在首次使用之前都不会被实例化。这只是一个细节,因为任何初始化A.a的尝试都会导致尝试在初始化完成之前重新访问静态属性(导致很可能是线程死锁)。

当您调用_ = A.a时,您将启动一个调用链,该调用链在完成初始化之前尝试获取A.a的引用。

// instantiate A.a
_ = A.a

// -> calls init() of A
// -> calls setB() of A
// -> instantiates B.b
// -> calls init() of B
// -> calls setA() of B
// -> tries to grab a reference to A.a, but the
//    initialization of A.a has not yet finished!
//    (as you can see by the lack of printing "2").

我不知道这意味着什么(未定义的行为?),但可能,因为A.a的初始化尚未完成,A.asetA()中的访问权限B会认为是时候实例化A.a,重新启动调用链,再次尝试在初始化之前访问A.a;导致一个递归的调用链循环(它将“冻结”并最终导致游乐场崩溃)。

另一个可能更合理的情况,正如下面我的@MartinR所指出的那样,我们实际上并没有进入递归调用链而是一个线程死锁(由于静态属性的线程安全性),所以我们尝试在完全初始化之前访问A.a

在任何情况下,核心问题都是在完全初始化之前尝试访问A.a