【问题标题】:Example of dispatch_once in SwiftSwift 中的 dispatch_once 示例
【发布时间】:2015-07-05 22:47:13
【问题描述】:

有没有一个例子说明应该如何在 Swift 中使用 dispatch_once? (最好是来自 Apple 的。)

注意:在这种情况下,我是not using it for a singleton;我想只运行一次任意代码。

更新:我主要对在实例方法中使用 this 时推荐的约定感兴趣,但在类方法、函数和全局上下文中的使用会有助于完整性。

【问题讨论】:

标签: swift cocoa swift2


【解决方案1】:

dispatch_once_t 是类型别名 (Int)。头文件:

/*!
 * @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

这是来自dispatch_once 文档的引用:

谓词必须指向存储在全局或静态中的变量 范围。使用自动或动态谓词的结果 存储(包括 Objective-C 实例变量)未定义。

token 变量必须存储在全局/静态范围内,并且必须初始化为零,这导致了这段代码:

import Foundation

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

如果您省略 = 0token 初始化),它将不起作用,因为编译器会产生错误在初始化之前获取变量“token”的地址,尽管静态和全局变量默认为零。在 Xcode 7B2 中测试。


更多示例基于评论。如果您在 class 方法中,则有多种可能性。

您不能在方法内声明静态属性,否则编译器会产生静态属性只能在类型错误时声明。这不起作用:

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

必须在类型上声明。这是在 Swift 1.2 (Xcode 6.3 IIRC) 中引入的。

现在允许在类中使用“静态”方法和属性(作为 “类决赛”的别名)。您现在可以声明静态存储 类中的属性,具有全局存储并且是惰性的 在首次访问时初始化(如全局变量)。现在的协议 将类型要求声明为“静态”要求,而不是 将它们声明为“类”要求。 (17198298)

那么,如果我们不喜欢全局变量,我们该怎么办?

类型上的静态变量

class MyClass {
  private static var token: dispatch_once_t = 0

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

结构体中的静态方法

不喜欢你班级的静态属性?想在您的方法中使用它吗?将其包装在这样的结构中:

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

实际上,我不知道任何 Apple 建议、最佳实践……如何为 dispatch_once 做这件事。只需使用您最喜欢、对您感觉良好且符合标准全局/静态范围的任何内容。

【讨论】:

  • 感谢您的信息!我希望看到的其中一个部分是 Apple 建议在您位于类的方法中时在全局/静态范围内创建令牌的约定。我正在澄清这个问题以反映这一点。
  • @Senseful 确实添加了更多示例。但我不知道任何苹果建议如何为dispatch_once 做这件事。只需满足全局/静态标准,然后选择你觉得好的。
  • 不知何故它不适用于 NSObject 子类的override class func initialize()dispatch_once 块内的代码没有被执行。
  • 这个答案中var token: dispatch_once_t = 0 用于创建令牌的第一部分是错误的,并且不会确保调度块只执行一次。令牌应创建为 type 属性,如下所示:struct Static { static var token: dispatch_once_t = 0 }。然后,可以进行调度调用:dispatch_once(&Static.token) { () -> Void in print("Called once") }
  • @sabajt 你从哪里弄错了?文档仍然声明 谓词必须指向存储在全局或静态范围内的变量。 因此,无论是静态的还是全局的。这个是全球性的。我错过了什么吗?
【解决方案2】:

对于那些好奇的人,对我来说,这种方法对此很有用:

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

    }
}

通过不声明静态变量,它具有预期的行为。话虽如此,这绝对不推荐用于任何严肃的项目,因为在文档中(正如您所说的那样)它指出:

谓词必须指向存储在全局或静态范围内的变量。使用带有自动或动态存储(包括 Objective-C 实例变量)的谓词的结果是未定义的。

如果我们不想在未来遇到任何奇怪的错误和未定义的行为,我会坚持 Apple 所说的。但是玩这些东西还是很好的,不是吗? =)

【讨论】:

  • 澄清一下,“预期行为”是指每个实例只运行一次 SomeVC?
  • @Senseful 是的,你是对的。至少这是我在提供答案时测试后观察到的行为
【解决方案3】:

robertvojta 的回答可能是最好的。因为我总是尽量避免导入 Foundation 并使用“纯”Swift 解决方案(使用 Swift3.0 我可以改变我的看法),我想与您分享我自己的非常简单的方法。我希望,这段代码是不言自明的

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

【讨论】:

    猜你喜欢
    • 2014-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-11
    • 2011-08-08
    • 1970-01-01
    相关资源
    最近更新 更多