Swift 3 GCD API更改后的dispatch_once

Dav*_*vid 77 grand-central-dispatch swift

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

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
    }
}
Run Code Online (Sandbox Code Playgroud)

这些是对libdispatch的更改.

Tod*_*ham 94

虽然使用延迟初始化全局变量对于某些一次初始化是有意义的,但对其他类型没有意义.将懒惰的初始化全局变量用于单例这样的事情是很有意义的,它对于保护混合设置这样的事情没有多大意义.

这是dispatch_once的Swift 3样式实现:

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是一个示例用法:

DispatchQueue.once(token: "com.vectorform.test") {
    print( "Do This Once!" )
}
Run Code Online (Sandbox Code Playgroud)

或使用UUID

private let _onceToken = NSUUID().uuidString

DispatchQueue.once(token: _onceToken) {
    print( "Do This Once!" )
}
Run Code Online (Sandbox Code Playgroud)

由于我们目前正处于从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()
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 你绝对不应该再使用 `objc_sync_enter` 和 `objc_sync_exit`。 (2认同)
  • 因此,您编写一个可重用的类,假设它不会被重用那么多:-) 如果不需要额外的努力来将时间复杂度从 O(N) 降低到 O(1),那么恕我直言,您应该始终这样做。 (2认同)

Der*_*123 59

来自doc:

Dispatch
Swift中不再提供免费函数dispatch_once.在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.
Run Code Online (Sandbox Code Playgroud)

  • `dispatch_once`很清楚.不幸的是,这很丑陋而令人困惑. (11认同)
  • 这是引入第三方依赖项时产生的技术债务,@ Tinkerbell.我喜欢Swift,但是因为这个原因引入了使用它的外部依赖关系. (4认同)
  • 这并不是说你不知道Swift会快速变化而你必须在Swift版本之间纠正很多破碎的代码. (3认同)
  • 最大的痛苦是第三方豆荚并不总是兼容Swift3. (2认同)

Vap*_*olf 57

扩展了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()
    }
}
Run Code Online (Sandbox Code Playgroud)

因此可以更简单地调用:

DispatchQueue.once {
    setupUI()
}
Run Code Online (Sandbox Code Playgroud)

如果您愿意,您仍然可以指定令牌:

DispatchQueue.once(token: "com.hostname.project") {
    setupUI()
}
Run Code Online (Sandbox Code Playgroud)

如果你在两个模块中有相同的文件,我想你可能会发生碰撞.太糟糕了没有#module


Rya*_*ner 17

编辑

@Frizlab的回答 - 这个解决方案不保证是线程安全的.如果这是至关重要的,应该使用替代方案

简单的解决方案

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

    self.title = "Hello Lazy Guy"

    return
}()
Run Code Online (Sandbox Code Playgroud)

用过像

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}
Run Code Online (Sandbox Code Playgroud)

  • Downvoted是因为这段代码肯定不像dispatch_once那样语义相同.dispatch_once确保代码只运行一次,**从**中调用它的任何线程.Lazy vars在多线程环境中具有未定义的行为. (3认同)

mxc*_*xcl 8

如果添加桥接标题,您仍然可以使用它:

typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);
Run Code Online (Sandbox Code Playgroud)

然后在.m某个地方:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
    dispatch_once(predicate, block);
}
Run Code Online (Sandbox Code Playgroud)

你现在应该可以使用mxcl_dispatch_onceSwift了.

大多数情况下你应该使用Apple提出的建议,但是我有一些合法的用途,我需要dispatch_once在两个函数中使用单个令牌,而Apple没有提供它.


Vla*_*lad 6

Swift 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)
   }
}
Run Code Online (Sandbox Code Playgroud)

用法:

class MyViewController: UIViewController {

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

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

}
Run Code Online (Sandbox Code Playgroud)

更新(2017年4月28日):OSSpinLock替换为os_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)
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!示例代码已更新.`OSSpinLock`替换为`os_unfair_lock`.顺便说一句:这是关于`Concurrent Programming`的一个很好的WWDC视频:https://developer.apple.com/videos/play/wwdc2016/720/ (2认同)

Bog*_*kov 5

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

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

然后在任何地方调用:

doOnce()
Run Code Online (Sandbox Code Playgroud)

  • 惰性变量的作用域是类,所以这绝对不会像dispatch_once那样。它将针对底层类的每个实例执行一次。将其移到类之外 [ private var doOnce: ()-&gt;() = { } ] 或将其标记为静态 [ static private var doOnce: ()-&gt;() = { } ] (3认同)
  • 这是一个非常好的解决方案!优雅、简短、清晰 (2认同)