swiftgrand-central-dispatch

How do I execute code once and only once in Swift?


The answers I've seen so far (1, 2, 3) recommend using GCD's dispatch_once thus:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

Output:

This is printed only on the first call to test()
This is printed for each call to test()

But wait a minute. token is a variable, so I could easily do this:

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
        print("This is printed only on the first call to test()")
    }
    print("This is printed for each call to test()")
}
test()

token = 0

test()

Output:

This is printed only on the first call to test()
This is printed for each call to test()
This is printed only on the first call to test()
This is printed for each call to test()

So dispatch_once is of no use if we I can change the value of token! And turning token into a constant is not straightforward as it needs to of type UnsafeMutablePointer<dispatch_once_t>.

So should we give up on dispatch_once in Swift? Is there a safer way to execute code just once?


Solution

  • Static properties initialized by a closure are run lazily and at most once, so this prints only once, in spite of being called twice:

    /*
    run like:
    
        swift once.swift
        swift once.swift run
    
    to see both cases
    */
    class Once {
        static let run: Void = {
            print("Behold! \(__FUNCTION__) runs!")
            return ()
        }()
    }
    
    if Process.arguments.indexOf("run") != nil {
        let _ = Once.run
        let _ = Once.run
        print("Called twice, but only printed \"Behold\" once, as desired.")
    } else {
        print("Note how it's run lazily, so you won't see the \"Behold\" text now.")
    }
    

    Example runs:

    ~/W/WhenDoesStaticDefaultRun> swift once.swift
    Note how it's run lazily, so you won't see the "Behold" text now.
    ~/W/WhenDoesStaticDefaultRun> swift once.swift run
    Behold! Once runs!
    Called twice, but only printed "Behold" once, as desired.