dispatch_once after the Swift 3 GCD API changes

后端 未结 9 1657
忘掉有多难
忘掉有多难 2020-11-28 20:48

What is the new syntax for dispatch_once in Swift after the changes made in language version 3? The old version was as follows.

var token: dispa         


        
相关标签:
9条回答
  • 2020-11-28 21:05

    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!
        }
    }
    
    0 讨论(0)
  • 2020-11-28 21:06

    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()
        }
    
    }
    
    0 讨论(0)
  • 2020-11-28 21:06

    I improve above answers get result:

    import Foundation
    extension DispatchQueue {
        private static var _onceTracker = [AnyHashable]()
    
        ///only excute once in same file&&func&&line
        public class func onceInLocation(file: String = #file,
                               function: String = #function,
                               line: Int = #line,
                               block: () -> Void) {
            let token = "\(file):\(function):\(line)"
            once(token: token, block: block)
        }
    
        ///only excute once in same Variable
        public class func onceInVariable(variable:NSObject, block: () -> Void){
            once(token: variable.rawPointer, 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: AnyHashable,block: () -> Void) {
            objc_sync_enter(self)
            defer { objc_sync_exit(self) }
    
            guard !_onceTracker.contains(token) else { return }
    
            _onceTracker.append(token)
            block()
        }
    
    }
    
    extension NSObject {
        public var rawPointer:UnsafeMutableRawPointer? {
            get {
                Unmanaged.passUnretained(self).toOpaque()
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-28 21:12

    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.
    
    0 讨论(0)
  • 2020-11-28 21:15

    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)
       }
    }
    
    0 讨论(0)
  • 2020-11-28 21:16

    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

    0 讨论(0)
提交回复
热议问题