Skip to content

Instantly share code, notes, and snippets.

@rnapier
Last active April 20, 2022 16:12
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rnapier/417153734150208dc8a18e3d6318255e to your computer and use it in GitHub Desktop.
Save rnapier/417153734150208dc8a18e3d6318255e to your computer and use it in GitHub Desktop.
A function that only executes once. Good Swift or over-clever?
// Swift3 gets rid of dispatch_once and recommends replacing it with a lazy global.
// That's very straightforward when dispach_once is used to initialize something, but
// isn't an exact match when you want something to execute once, and then become a noop
// in a thread-safe way.
// The following approach seems completely "correct" and I guess actually a bit elegant,
// if by "elegant" you mean "terse and not immediately obvious to the reader, which makes
// you look very clever."
var doOnce: () -> Void = {
// The side effect that should only happen once
print("Running once, I hope")
// A dummy function that's evaluated every time we're accessed
return {}
}()
doOnce() // Prints
doOnce() // does not print
doOnce() // does not print
// So is this good Swift that readers should simply learn to understand, or is it over-clever Swift
// that has a better answer that is both idempotent and threadsafe?
// I know the docs suggest just "evaluating a var with _ =" but that seems horrible compared to having a clear
// function-call syntax.
@callionica
Copy link

Note that Swift global vars are lazy, but vars in the IBM Sandbox (https://swiftlang.ng.bluemix.net/#/repl) are not global. If you execute this code in the sandbox, "Running once" is indeed printed only once, but it's printed at line 16, not line 18. This difference could be significant for people trying to ensure that the initialization happens on the first call to the function and not before. People can see the difference in behavior by adding a print to the dummy inner function.

@mpw
Copy link

mpw commented Nov 5, 2016

Hmm...but this runs the print() before the first invocation of doOnce(). Is that the way it is intended?

var doOnce: () -> Void = {
// The side effect that should only happen once
print("Running once, I hope")

// A dummy function that's evaluated every time we're accessed
return {}

}()

print("before")
doOnce()
doOnce()
doOnce()

marcel@sarek[tmp]./once
Running once, I hope
before

@ptrkstr
Copy link

ptrkstr commented Aug 16, 2018

@mpw that looks like it's running as intended.

To simplify it I recommend the following typealias.

/// Useful when wanting to initialise something only once.
/// Use `return {}` at the end of the closure.
/// Example usage:
///     ```
///     static var initialize: PerformOnce = {
///         print("hello world")
///         return {}
///     }()
///     ```
typealias PerformOnce = () -> Void

Usage

struct AwesomeStruct {
    static var initialize: PerformOnce = {
        Print("Hello World")
        return {}
    }()
}

@claybridges
Copy link

Reinforcing @mpw, this code as-is prints on var instantiation, before even the first doOnce() call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment