18

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?

nhgrif
  • 61,578
  • 25
  • 134
  • 173
Eric
  • 16,003
  • 15
  • 87
  • 139
  • Objective-C has the same problem. The idea is to put the `token` in the same scope as the `dispatch_once` block (and give it a better name like `onceToken` and place it RIGHT above the `dispatch_once` block itself so that it's very clear). – nhgrif Dec 10 '15 at 13:55
  • well then `dispatch_once` is no safer than using an ordinary boolean variable. – Eric Dec 10 '15 at 13:55
  • http://stackoverflow.com/q/25354882/2792531 – nhgrif Dec 10 '15 at 13:56
  • Eric, a question about using an ordinary bool versus `dispatch_once` would probably be a *much* better discussion for Stack Overflow. I'd quite like to see that answer (if it hasn't already been asked & answered here). – nhgrif Dec 10 '15 at 14:00
  • The newest answers (as in your references #1 and #3) do *not* recommend GCD but a a static class property (which is lazily initialised in a thread-safe manner). – Martin R Dec 10 '15 at 14:09
  • IMPORTANT: change Process to CommandLine after Swift 3.0 or else you'll get an error. Another one of Swifts 100's of unnecessary deprecation smh – Ben Akin Mar 22 '18 at 00:19

5 Answers5

25

A man went to the doctor, and said "Doctor, it hurts when I stamp on my foot". The doctor replied, "So stop doing it".

If you deliberately alter your dispatch token, then yes - you'll be able to execute the code twice. But if you work around the logic designed to prevent multiple execution in any way, you'll be able to do it. dispatch_once is still the best method to ensure code is only executed once, as it handles all the (very) complex corner cases around initialisation and race conditions that a simple boolean won't cover.

If you're worried that someone might accidentally reset the token, you can wrap it up in a method and make it as obvious as it can be what the consequences are. Something like the following will scope the token to the method, and prevent anyone from changing it without serious effort:

func willRunOnce() -> () {
    struct TokenContainer {
        static var token : dispatch_once_t = 0
    }

    dispatch_once(&TokenContainer.token) {
        print("This is printed only on the first call")
    }
}
Adam Wright
  • 48,938
  • 12
  • 131
  • 152
  • 4
    First line alone was probably all you needed. The rest of the answer is redundant. ;) – nhgrif Dec 10 '15 at 13:58
  • LOL. Best explanation . – Kumar KL Dec 10 '15 at 13:59
  • I fundamentally disagree. The documentation for `dispatch_once` states that it "Executes a block object once and only once for the lifetime of an application." No ambiguity here, the API is breaking the contract. And that doctor is incompetent. – Eric Dec 10 '15 at 14:07
  • 1
    @Eric: Have a look at this answer from Greg Parker (from Apple) http://stackoverflow.com/a/19845164/1187415: *"The implementation of dispatch_once() requires that the dispatch_once_t is zero, and **has never been non-zero.** ..."* – Martin R Dec 10 '15 at 14:16
  • Greg adds: "Instance variables are initialized to zero, but their memory may have previously stored another value. This makes them unsafe for dispatch_once() use." Enough said... – Eric Dec 10 '15 at 14:18
  • Not possible anymore: `'dispatch_once_t' is unavailable in Swift: Use lazily initialized globals instead` – Michal Cichon Oct 26 '22 at 08:44
24

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.
Jeremy W. Sherman
  • 35,901
  • 5
  • 77
  • 111
  • This works solidly. It's just a bit cumbersome to have to wrap it in a Type, but that corresponds to a lot of uses anyway. – Eric Dec 10 '15 at 14:48
  • 3
    You don't need to wrap a static property in a type. You can declare a global variable or constant and initialize it with a closure. The closure will be called lazily once. The exception is that if the global variable/constant is defined in main.swift, it will still be executed once but in order of definition (i.e. not lazily). – Tom Pelaia Dec 10 '15 at 14:59
  • 2
    As an aside, this is actually using 'dispatch_once' in the generated code :). It's just hiding it in the language semantics. – Adam Wright Dec 10 '15 at 15:27
  • 2
    @AdamWright: It uses `dispatch_once` on OS X and `pthread_once` under Linux. The advantage of pushing it into the language is that it prevents anyone accessing and messing with the token tracking execution state, which was OP's problem. – Jeremy W. Sherman Dec 11 '15 at 16:13
  • I hate to see this to be done over a property and not method `Once.run()` – Saran Oct 25 '18 at 14:46
7

I think the best approach is to just construct resources lazily as needed. Swift makes this easy.

There are several options. As already mentioned, you can initialize a static property within a type using a closure.

However, the simplest option is to define a global variable (or constant) and initialize it with a closure then reference that variable anywhere the initialization code is required to have happened once:

let resourceInit : Void = {
  print("doing once...")
  // do something once
}()

Another option is to wrap the type within a function so it reads better when calling. For example:

func doOnce() {
    struct Resource {
        static var resourceInit : Void = {
            print("doing something once...")
        }()
    }

    let _ = Resource.resourceInit
}

You can do variations on this as needed. For example, instead of using the type internal to the function, you can use a private global and internal or public function as needed.

However, I think the best approach is just to determine what resources you need to initialize and create them lazily as global or static properties.

Tom Pelaia
  • 1,275
  • 10
  • 9
1

For anyone who stumbles on this thread... We ran into a similar situation at Thumbtack and came up with this: https://www.github.com/thumbtack/Swift-RunOnce. Essentially, it lets you write the following

func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated: Bool)
  
    runOnce {
      // One-time code
    }
}

I also wrote a blog post explaining how the code works, and explaining why we felt it was worth adding to our codebase.

Plastech
  • 757
  • 6
  • 17
0

I found this while searching for something similar: Run code once per app install. The above solutions only work within each app run. If you want to run something once across app launches, do this:

func runOnce() {
    if UserDefaults.standard.object(forKey: "run_once_key") == nil {
        UserDefaults.standard.set(true, forKey: "run_once_key")
        /* run once code */
    } else {
        /* already ran one time */
    }
}

If the app is deleted and re-installed, this will reset.

Use NSUbiquitousKeyValueStore for tracking a value across installs and devices as long as user using same appleID.