Swift 3 GCD API更改后的dispatch_once


83

dispatch_once语言版本3进行更改后,Swift中的新语法是什么?旧版本如下。

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
    }
}

这些是对libdispatch所做的更改



基于问题stackoverflow.com/a/38311178/1648724stackoverflow.com/a/39983813/1648724,我创建了一个CocoaPod来做到这一点:pod 'SwiftDispatchOnce', '~> 1.0'干杯。:]
JRG-Developer's

Answers:


69

文档

派遣
自由功能dispatch_once在Swift中不再可用。在Swift中,您可以使用延迟初始化的全局变量或静态属性,并获得与提供的dispatch_once相同的线程安全性和一次调用保证。例:

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.

3
这并不是好像您不知道Swift会迅速变化,并且您将不得不在各个Swift版本之间更正许多损坏的代码。
阿比森

2
最大的麻烦是与Swift3并不总是兼容的3rd pod。
Tinkerbell

4
这就是您在引入第三方依赖项@Tinkerbell时应承担的技术债务。我喜欢Swift,但是要非常谨慎地引入使用它的外部依赖关系,这正是这个原因。
克里斯·瓦格纳

17
dispatch_once很清楚。这不幸的是,丑陋和混乱..
亚历山大摹

104

虽然使用惰性初始化的全局变量可以进行一次初始化,但对于其他类型则没有意义。对于诸如单例之类的东西使用惰性初始化的全局变量是很有意义的,而对于诸如防止麻烦的设置之类的事情则没有太大的意义。

这是一个Swift 3样式的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()
    }
}

这是一个示例用法:

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

或使用UUID

private let _onceToken = NSUUID().uuidString

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

由于我们目前正处于从Swift 2到3的过渡时期,因此下面是一个Swift 2实现示例:

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()
    }

}

非常感谢您的解决方案。我确实陷入了麻烦的设置中。我希望迅速的团队解决这个用例。
salman140 '16

2
你绝对不应该使用objc_sync_enterobjc_sync_exit了。
smat88dd

1
为什么是这样?
Tod Cunningham

1
为了提高性能,应为_onceTrackers使用集合而不是数组。这样可以将时间复杂度从O(N)提高到O(1)。
Werner Altewischer '17

2
因此,您假设它不会被重用,则编写一个可重用的类:-)如果不需要额外的努力将时间复杂度从O(N)降低到O(1),则应该始终这样做。
Werner Altewischer

62

在上述Tod Cunningham的答案的基础上,我添加了另一种方法,该方法可自动从文件,函数和行中生成令牌。

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()
    }
}

因此,调用起来可能更简单:

DispatchQueue.once {
    setupUI()
}

并且您仍然可以根据需要指定令牌:

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

我想如果两个模块中有相同的文件,可能会发生冲突。太可惜了没有#module


这为正在发生的事情提供了更多的启示。谢谢。
nyxee


真的帮了感谢名单
Manjunath C.Kadani

19

编辑

@Frizlab的答案-不能保证此解决方案是线程安全的。如果这很关键,则应使用替代方法

简单的解决方案是

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

    self.title = "Hello Lazy Guy"

    return
}()

使用像

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

1
这根本没有帮助,因为不能在常规代码中内嵌惰性var声明,因此必须在struct或class定义中。这意味着dispatchOnce的内容无法捕获实例的周围范围。例如,如果您声明一个尚未运行的闭包,则无法在该闭包中声明结构,而让懒惰的var的内容成为另一个闭包,该闭包从周围的闭包中捕获var ...
CommaToast

3
Downvoted因为这段代码有绝对相同的语义dispatch_once。dispatch_once确保代码运行一次,无论您从哪个线程调用它。惰性var在多线程环境中具有未定义的行为。
Frizlab

在这种解决方案中,在某些情况下,init块将调用两次
神圣的

8

如果添加桥接头,仍然可以使用它:

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

然后在.m某个地方:

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

您现在应该可以在mxcl_dispatch_onceSwift中使用了。

通常,您应该使用Apple的建议,但是我有一些合法的用途,我需要dispatch_once在两个函数中使用单个令牌,而Apple所提供的却没有涵盖。


7

您可以像这样声明一个顶级变量函数:

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

然后在任何地方调用:

doOnce()

1
惰性变量的作用域为该类,因此,这绝对不会像dispatch_once那样起作用。它将对基础类的每个实例执行一次。或者将其移到类之外[private var doOnce:()->()= {}]或将其标记为静态[static private var doOnce:()->()= {}]
Eli Burke

1
完全正确!谢谢。在大多数情况下,每个实例需要执行一次操作。
波格丹诺维科夫

2
这是一个非常好的解决方案!优雅,短而清晰
Ben Leggiero

6

雨燕3:对于那些喜欢可重用的类(或结构)的人:

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)
   }
}

用法:

class MyViewController: UIViewController {

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

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

}

更新(2017年4月28日):OSSpinLockos_unfair_lockmacOS 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)
   }
}

我收到一条消息,说明iOS 10.0中不推荐使用
OSSSpinLock

2
谢谢!示例代码已更新。OSSpinLock替换为os_unfair_lock。顺便说一句:这是关于WWDC的一个很好的视频,关于Concurrent Programmingdeveloper.apple.com/videos/play/wwdc2016/720
Vlad17年

0

我改善以上答案得到结果:

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()
        }
    }
}

-3

如果使用的是Swift 1.2或更高版本,请使用类常量方法;如果需要支持早期版本,请使用嵌套的struct方法。在Swift中探索Singleton模式。下面的所有方法都支持延迟初始化和线程安全。Swift 3.0无法使用dispatch_once方法

方法A:类常量

class SingletonA {

    static let sharedInstance = SingletonA()

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

}

方法B:嵌套结构

class SingletonB {

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

}

方法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!
    }
}

1
这个问题专门询问了Swift 3的解决方案。
summersign
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.