Подтвердить что ты не робот

Пример dispatch_once в Swift

Есть ли пример использования dispatch_once в Swift? (Желательно от Apple.)

Примечание: в этом случае я не использую его для одноэлементного; Я хочу запустить произвольный код ровно один раз.

Обновление. Меня в основном интересует соглашение, рекомендуемое при использовании этого метода экземпляра, но использование в методе класса, функции и в глобальном контексте было бы полезно для полноты.

4b9b3361

Ответ 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) undefined.

Переменная токена должна храниться в глобальной/статической области и должна быть инициализирована до нуля, что приводит к этому коду:

import Foundation

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

Это не работает, если вы опускаете = 0 (token инициализацию), потому что компилятор дает ошибку Адрес переменной "токен", принятый до его инициализации, несмотря на то, что статика и глобальные значения по умолчанию равны нулю. Протестировано в Xcode 7B2.


Дополнительные примеры на основе комментариев. Если вы находитесь внутри метода class, у вас есть несколько возможностей.

Вы не можете объявлять статическое свойство внутри метода, иначе компилятор дает Статические свойства могут быть объявлены только при ошибке типа. Это не работает:

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

Должен быть объявлен по типу. Это было введено в Swift 1.2 (Xcode 6.3 IIRC).

"статические" методы и свойства теперь разрешены в классах (как псевдоним для "final class" ). Теперь вам разрешено объявлять статические данные свойства в классах, которые имеют глобальное хранилище и ленивы инициализируется при первом доступе (например, глобальные переменные). Протоколы сейчас объявлять требования типа как "статические" требования вместо заявляя их как "классные" требования. (17198298)

Итак, что мы можем сделать, если нам не нравятся глобальные переменные?

Статическая переменная типа

class MyClass {
  private static var token: dispatch_once_t = 0

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

Статический метод, заключенный в структуру

Не нравится статическое свойство в классе yur? Хочешь иметь это в своем методе? Оберните его в структуру следующим образом:

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

На самом деле я не знаю ни одной рекомендации Apple, лучшей практики... как это сделать для dispatch_once. Просто используйте то, что вам больше всего нравится, чувствует себя хорошо и просто отвечайте критериям глобальной/статической области.

Ответ 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()
        }

    }
}

Не объявляя статический var, он имеет ожидаемое поведение. При этом это НЕ РЕКОМЕНДУЕТСЯ для любого серьезного проекта, поскольку в Документе (как сказано в вашем заявлении) говорится:

Предикат должен указывать на переменную, хранящуюся в глобальной или статической области. Результатом использования предиката с автоматическим или динамическим хранилищем (включая переменные экземпляра Objective-C) является undefined.

Если мы не хотим запускать какие-либо будущие странные ошибки и поведение undefined, я бы просто придерживался того, что говорит Apple. Но все же приятно играть с этими вещами, не так ли? =)

Ответ 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()