Swift 函数中的常量(带计算)?

Swift constants (with a calculation) in functions?

这是一个简单的 Swift 函数

fileprivate func test()->String{
    let c = Array("abc".characters)
    let k = UInt32(c.count)
    let r = Int(arc4random_uniform(k))
    return String(c[r])
}

(我选择这个例子是因为,很明显,你可能会调用数十亿次来生成某种输出;所以你可能会担心设置这两个常量的性能。)

请注意,它必须进行一些计算才能得到 c,实际上要得到 k,它必须使用 c.

我的问题很简单:每次调用这个函数

test()
test()
test()

实际上每次我调用它时,它都会计算 k、and/or c 或者实际上它们只是 计算一次?

(如果 "only once",那么出于好奇:我第一次调用该函数时它会这样做吗?或者编译器可能安排在启动时单独完成它?或者就此而言它知道它可以在编译期间计算它们?)


我经常使用全局计算属性,就像这样

let basicDF : DateFormatter = {
    print("this will only be done once per launch of the app")
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd"
    return formatter 
}()

(也许与fileprivate)如果上述问题的答案是"no, c and 'k' are calculated every time you call test",那么在那种情况下你怎么能把一种静态计算属性放在一个函数中呢? ?

我认为你应该假设每次都计算ck。计算可能会作为编译器的实现细节进行优化,但如果我是你,我不会指望它。

Swift 没有等效于 C static 局部变量(即函数中的 "automatic" 变量,其值在函数调用之间保持不变)。

如果您真的想努力确保 k 只计算一次,请将其设为真正的常量(即在 class 级别)。当然,您还必须对 c 执行相同的操作,因为您在以后的计算中需要它。在这种情况下,这似乎是一个愚蠢的例子,但我经常在创建的东西是重量级的时候这样做,比如将被反复使用的图像或视图:

class MyView : UIView {
    lazy var arrow : UIImage = self.arrowImage()
    func arrowImage () -> UIImage {
        // ... big image-generating code goes here ...
    }
}

我一遍又一遍地调用 arrowImage() 直到我对自己说,等等,我可以把它变成一个常数(这里表示为 lazy var)并且只计算一次,即第一个访问时间 arrow

不,在您的特定情况下,编译器当前不会将 c and/or k 优化为仅计算一次的常量表达式(这可以通过 examining the IR 在优化的构建中)——尽管这一切都可能随着语言的未来版本而改变。

但是值得注意的是,它目前可以对更简单的表达式执行此操作,例如:

func foo() -> Int {
    return 2 + 4
}

编译器可以在编译时评估加法,所以函数只是 return 6 (然后可以内联)。但是当然,如​​果您确实将给定函数确定为性能瓶颈,那么您首先应该只担心此类优化。

在函数中获取静态常量的一个好技巧是在函数范围内定义一个带有静态属性的无大小写 enum,您可以在其中定义常量表达式:

func test() -> String {

    enum Constants {
        static let c = Array("abc".characters)
        static let k = UInt32(c.count)
    }

    let r = Int(arc4random_uniform(Constants.k))
    return String(Constants.c[r])
}

现在 ck 的初始化表达式都将只计算一次,这将在它们首次使用时完成(即函数首次调用时)。

当然,正如您所展示的,您可以使用立即计算的闭包来获得多行初始化表达式:

enum Constants {
    static let c: [Character] = {
        // ...
        return Array("abc".characters)
    }()
    // ...
}