How do I execute code once and only once in Swift?
Asked Answered
M

6

20

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?

Malkamalkah answered 10/12, 2015 at 13:51 Comment(6)
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).Unfinished
well then dispatch_once is no safer than using an ordinary boolean variable.Malkamalkah
https://mcmap.net/q/204189/-static-function-variables-in-swift/2792531Unfinished
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).Unfinished
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).Entranceway
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 smhChalcidice
I
26

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.
Ironic answered 10/12, 2015 at 14:35 Comment(5)
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.Malkamalkah
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).Citral
As an aside, this is actually using 'dispatch_once' in the generated code :). It's just hiding it in the language semantics.Kemerovo
@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.Ironic
I hate to see this to be done over a property and not method Once.run()Sparke
K
27

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")
    }
}
Kemerovo answered 10/12, 2015 at 13:57 Comment(6)
First line alone was probably all you needed. The rest of the answer is redundant. ;)Unfinished
LOL. Best explanation .Barboza
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.Malkamalkah
@Eric: Have a look at this answer from Greg Parker (from Apple) https://mcmap.net/q/15512/-can-i-declare-dispatch_once_t-predicate-as-a-member-variable-instead-of-static: "The implementation of dispatch_once() requires that the dispatch_once_t is zero, and has never been non-zero. ..."Entranceway
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...Malkamalkah
Not possible anymore: 'dispatch_once_t' is unavailable in Swift: Use lazily initialized globals insteadParang
I
26

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.
Ironic answered 10/12, 2015 at 14:35 Comment(5)
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.Malkamalkah
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).Citral
As an aside, this is actually using 'dispatch_once' in the generated code :). It's just hiding it in the language semantics.Kemerovo
@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.Ironic
I hate to see this to be done over a property and not method Once.run()Sparke
C
8

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.

Citral answered 10/12, 2015 at 15:23 Comment(0)
B
2

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.

Byrnie answered 3/1, 2023 at 23:41 Comment(0)
Z
1

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.

Zap answered 19/1, 2023 at 4:58 Comment(0)
N
0

If you would like to execute a block of code on first call (once) and another block on another calls then you need a function which returns a bool value.

public class FunctionCallTracker {

    private var store: Set<String> = []

    public init() { }

    public func isFirstTimeCall(_ fileId: String = #fileID, _ line: Int = #line, _ column: Int = #column) -> Bool {
        let funcIdentifier = "\(fileId):\(line):\(column)"
        if store.contains(funcIdentifier) {
            return false
        } else {
            store.insert(funcIdentifier)
            return true
        }
    }
}

Example of usage:

class ViewController: UIViewController {

    private let funcTracker = FunctionCallTracker()

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated: animated)
            if funcTracker.isFirstTimeCall() {
                // do something only on first call of viewWillAppear
            } else {
                // do something on other calls of viewWillAppear
            }
    }

    override func updateViewConstraints() {
        super.updateViewConstraints()
            if funcTracker.isFirstTimeCall() {
                // do something only on first call of updateViewConstraints
            } else {
                // do something on other calls of updateViewConstraints
            }
    }
}
Nasa answered 17/6 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.