Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift constants (with a calculation) in functions?

Here's a simple Swift function

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

(I chose this example because, obviously, it's something you may call billions of times to generate some sort of output; so you might be concerned for performance in setting up the two constants.)

Note that it has to do a bit of calculation to get c, and indeed to get k it has to use c.

My question is simple: every time you call this function

test()
test()
test()

in fact does it calculate k, and/or c every time I call it, or indeed are they only calculated once?

(if "only once", then as a curiosity: it does that the first time I call the function? or perhaps the compiler arranges to have it done separately at startup time? or for that matter does it know it can compute them during compilation?)


I often use global calculated properties, rather like this

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 
}()

(perhaps with fileprivate) If the answer to the above question is "no, c and 'k' are calculated every time you call test", then in that case how can you put a kind of static computed property inside a function??

like image 768
Fattie Avatar asked May 06 '17 14:05

Fattie


1 Answers

No, in your particular case, the compiler currently doesn't optimise c and/or k to constant expressions that are only evaluated once (this can be seen by examining the IR in an optimised build) – although this is all subject to change with future versions of the language.

However it's worth noting that it can currently do this for simpler expressions, for example:

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

The compiler can evaluate the addition at compile-time so the function just does return 6 (and then this can be inlined). But of course, you should only be worrying about such optimisations in the first place if you've actually identified the given function as a performance bottleneck.

One nice trick to get static constants in a function is to define a case-less enum with static properties in the function scope, in which you can define your constant expressions:

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])
}

Now both the initialiser expressions for c and k will only be evaluated once, and this will be done when they are first used (i.e when the function is first called).

And of course, as you show, you can use an immediately-evalutated closure in order to have a multi-line initialisation expression:

enum Constants {
    static let c: [Character] = {
        // ...
        return Array("abc".characters)
    }()
    // ...
}
like image 80
Hamish Avatar answered Oct 21 '22 10:10

Hamish