13

Is there an example of how dispatch_once should be used in Swift? (Preferably one from Apple.)

Note: In this case, I'm not using it for a singleton; I want to run arbitrary code exactly once.

Update: I'm mainly interested in the convention recommended when using this in an instance method, but usage in a class method, function, and in the global context would be useful for completeness sake.

Community
  • 1
  • 1
Senseful
  • 86,719
  • 67
  • 308
  • 465

3 Answers3

28

dispatch_once_t is type alias (Int). Header documentation:

/*!
 * @typedef dispatch_once_t
 *
 * @abstract
 * A predicate for use with dispatch_once(). It must be initialized to zero.
 * Note: static and global variables default to zero.
 */
typealias dispatch_once_t = Int

And here's the quote from dispatch_once documentation:

The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage (including Objective-C instance variables) is undefined.

Token variable must be stored in global / static scope and must be initialized to zero, which leads to this code:

import Foundation

var token: dispatch_once_t = 0
dispatch_once(&token) { () -> Void in
  print("Called once")
}

It doesn't work if you omit = 0 (token initialization), because compiler yields error Address of variable 'token' taken before it is initialized despite the fact that statics and globals default to zero. Tested in Xcode 7B2.


More examples based on comment. If you're inside class method you have several possibilities.

You can't declare static property inside method otherwise compiler yields Static properties may only be declared on a type error. This doesn't work:

class func doItOnce() {
  static var token: dispatch_once_t = 0
  ...
}

Must be declared on a type. This was introduced in Swift 1.2 (Xcode 6.3 IIRC).

“static” methods and properties are now allowed in classes (as an alias for “class final”). You are now allowed to declare static stored properties in classes, which have global storage and are lazily initialized on first access (like global variables). Protocols now declare type requirements as “static” requirements instead of declaring them as “class” requirements. (17198298)

So, what we can do if we don't like globals?

Static variable on a type

class MyClass {
  private static var token: dispatch_once_t = 0

  class func doItOnce() {
    dispatch_once(&token) {
      print("Do it once")
    }
  }
}

Static in a method wrapped in struct

Don't like static property on yur class? Would like to have it in your method? Wrap it in struct like this:

class func doItOnce() {
  struct Tokens { static var token: dispatch_once_t = 0 }
  dispatch_once(&Tokens.token) {
    print("Do it once")
  }
}

Actually I'm not aware of any Apple recommendation, best practice, ... how to do it for dispatch_once. Simply use whatever you like most, feels good to you and just meet criteria global / static scope.

zrzka
  • 20,249
  • 5
  • 47
  • 73
  • Thanks for the info! One of the parts I was hoping to see was the convention Apple recommends for creating the token in a global/static scope when you are inside a class's method. I'm clarifying the question to reflect this. – Senseful Jul 07 '15 at 00:08
  • @Senseful did add more examples. But I'm not aware of any Apple recommendation how to do it for `dispatch_once`. Just meet the criteria global / static and choose whatever feels good to you. – zrzka Jul 07 '15 at 06:34
  • Somehow it doesn't work with `override class func initialize()` of NSObject subclasses. The code inside the `dispatch_once` block is not executed. – Nicolas Miari Jan 20 '16 at 10:21
  • The first section in this answer where `var token: dispatch_once_t = 0` is used to create the token is wrong, and will not ensure the dispatch block is executed only once. The token should be created as a _type property_, like so: `struct Static { static var token: dispatch_once_t = 0 }`. Then, the dispatch call can be made: `dispatch_once(&Static.token) { () -> Void in print("Called once") }` – sabajt Apr 22 '16 at 20:34
  • @sabajt from where did you get it's wrong? Docs still states that _The predicate must point to a variable stored in global or static scope._ So, either static or global. This one is global. Did I miss something? – zrzka Apr 24 '16 at 16:37
  • Sorry, you are right @robertvojta: I was missing that the variable was stored in global scope, rather than a scope local to a method. One thing that is a a bit misleading about the first example is that in most cases trying to call an expression in the global scope is now allowed, yielding the error _Expressions are not allowed at the top level_. However declaring the variable in global scope, then calling `dispatch_once` inside a method will work fine. – sabajt Apr 25 '16 at 13:47
  • Under Swift 4 you get the following error: `'dispatch_once_t' is unavailable in Swift: Use lazily initialized globals instead` – ThomasW May 15 '18 at 02:52
4

For those of you who are curious, for me this approach has been useful for this purpose:

class SomeVC : UIViewController {
    private var token: dispatch_once_t = 0

    public override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)

        dispatch_once(&token) { () -> Void in
            self.doSomethingOnce()
        }

    }
}

By not declaring a static var it has the expected behaviour. That being said, this is definitely NOT RECOMMENDED for any serious project, since in the Docs (as your well said) it states:

The predicate must point to a variable stored in global or static scope. The result of using a predicate with automatic or dynamic storage (including Objective-C instance variables) is undefined.

If we don't want to run into any future weird bugs and undefined behaviour I would just stick to what Apple says. But it's still nice to play around with these things, isn't it? =)

Robertibiris
  • 844
  • 8
  • 15
  • Just to clarify, by "expected behavior," you mean running exactly once *per instance* of SomeVC? – Senseful Dec 29 '15 at 17:47
  • @Senseful yes, you are correct. At least that's the behavior I observed after testing it around the time I provided the answer – Robertibiris Jan 02 '16 at 20:03
-1

the robertvojta's answer is probably the best. because i try always to avoid import Foundation and use 'pure' Swift solution (with Swift3.0 i could change my opinion), I would like to share with you my own, very simple approach. i hope, this code is self-explanatory

class C {
    private var i: Int?
    func foo()->Void {
        defer {
            i = 0
        }
        guard i == nil else { return }
        print("runs once")
    }
}

let c = C()
c.foo() // prints "runs once"
c.foo()
c.foo()

let c1 = C()
c1.foo() // prints "runs once"
c1.foo()

class D {
    static private var i: Int?
    func foo()->Void {
        defer {
            D.i = 0
        }
        guard D.i == nil else { return }
        print("runs once")
    }
}

let d = D()
d.foo() // prints "runs once"
d.foo()
let d2 = D()
d.foo()
user3441734
  • 16,722
  • 2
  • 40
  • 59