dispatch_once after the Swift 3 GCD API changes

北战南征 提交于 2019-11-27 02:45:39

From the doc:

Dispatch
The free function dispatch_once is no longer available in Swift. In Swift, you can use lazily initialized globals or static properties and get the same thread-safety and called-once guarantees as dispatch_once provided. Example:

let myGlobal = { … global contains initialization in a call to a closure … }()
_ = myGlobal  // using myGlobal will invoke the initialization code only the first time it is used.

While using lazy initialized globals can make sense for some one time initialization, it doesn't make sense for other types. It makes a lot of sense to use lazy initialized globals for things like singletons, it doesn't make a lot of sense for things like guarding a swizzle setup.

Here is a Swift 3 style implementation of dispatch_once:

public extension DispatchQueue {

    private static var _onceTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String, block:@noescape(Void)->Void) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}

Here is an example usage:

DispatchQueue.once(token: "com.vectorform.test") {
    print( "Do This Once!" )
}

or using a UUID

private let _onceToken = NSUUID().uuidString

DispatchQueue.once(token: _onceToken) {
    print( "Do This Once!" )
}

As we are currently in a time of transition from swift 2 to 3, here is an example swift 2 implementation:

public class Dispatch
{
    private static var _onceTokenTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token token: String, @noescape block:dispatch_block_t) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTokenTracker.contains(token) {
            return
        }

        _onceTokenTracker.append(token)
        block()
    }

}

Expanding on Tod Cunningham's answer above, I've added another method which makes the token automatically from file, function, and line.

public extension DispatchQueue {
    private static var _onceTracker = [String]()

    public class func once(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String,
                           block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }
}

So it can be simpler to call:

DispatchQueue.once {
    setupUI()
}

and you can still specify a token if you wish:

DispatchQueue.once(token: "com.hostname.project") {
    setupUI()
}

I suppose you could get a collision if you have the same file in two modules. Too bad there isn't #module

Edit

@Frizlab's answer - this solution is not guaranteed to be thread-safe. An alternative should be used if this is crucial

Simple solution is

lazy var dispatchOnce : Void  = { // or anyName I choose

    self.title = "Hello Lazy Guy"

    return
}()

used like

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}

You can still use it if you add a bridging header:

typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);

Then in a .m somewhere:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
    dispatch_once(predicate, block);
}

You should now be able to use mxcl_dispatch_once from Swift.

Mostly you should use what Apple suggest instead, but I had some legitimate uses where I needed to dispatch_once with a single token in two functions and there is not covered by what Apple provide instead.

Swift 3: For those who likes reusable classes (or structures):

public final class /* struct */ DispatchOnce {
   private var lock: OSSpinLock = OS_SPINLOCK_INIT
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      OSSpinLockLock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      OSSpinLockUnlock(&lock)
   }
}

Usage:

class MyViewController: UIViewController {

   private let /* var */ setUpOnce = DispatchOnce()

   override func viewWillAppear() {
      super.viewWillAppear()
      setUpOnce.perform {
         // Do some work here
         // ...
      }
   }

}

Update (28 April 2017): OSSpinLock replaced with os_unfair_lock due deprecation warnings in macOS SDK 10.12.

public final class /* struct */ DispatchOnce {
   private var lock = os_unfair_lock()
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      os_unfair_lock_lock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      os_unfair_lock_unlock(&lock)
   }
}
Bogdan Novikov

You can declare a top-level variable function like this:

private var doOnce: ()->() = {
    /* do some work only once per instance */
    return {}
}()

then call this anywhere:

doOnce()
Shan Shafiq

Use the class constant approach if you are using Swift 1.2 or above and the nested struct approach if you need to support earlier versions. An exploration of the Singleton pattern in Swift. All approaches below support lazy initialization and thread safety. dispatch_once approach is not worked in Swift 3.0

Approach A: Class constant

class SingletonA {

    static let sharedInstance = SingletonA()

    init() {
        println("AAA");
    }

}

Approach B: Nested struct

class SingletonB {

    class var sharedInstance: SingletonB {
        struct Static {
            static let instance: SingletonB = SingletonB()
        }
        return Static.instance
    }

}

Approach C: dispatch_once

class SingletonC {

    class var sharedInstance: SingletonC {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: SingletonC? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = SingletonC()
        }
        return Static.instance!
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!