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

Куда dispatch_once в Swift 3?

Хорошо, поэтому я узнал о новом Swifty Dispatch API в Xcode 8. Мне весело с помощью DispatchQueue.main.async, и я просматривал модуль Dispatch в Xcode, чтобы найти все новые API.

Но я также использую dispatch_once для того, чтобы такие вещи, как создание одноэлементов и одноразовая настройка, не выполнялись более одного раза (даже в многопоточной среде)... и dispatch_once нигде не было найдено в новом диспетчерском модуле?

static var token: dispatch_once_t = 0
func whatDoYouHear() {
    print("All of this has happened before, and all of it will happen again.")
    dispatch_once(&token) {
        print("Except this part.")
    }
}
4b9b3361

Ответ 1

Так как Swift 1.x, Swift использует dispatch_once за кулисами для выполнения поточно-безопасной ленивой инициализации глобальных переменных и статических свойств.

Итак, вышеприведенный static var уже использовал dispatch_once, что делает его довольно странным (и, возможно, проблематичным использовать его снова как токен для другого dispatch_once). На самом деле нет надежного способа использования dispatch_once без такой рекурсии, поэтому они избавились от нее. Вместо этого используйте только встроенные функции языка:

// global constant: SomeClass initializer gets called lazily, only on first use
let foo = SomeClass()

// global var, same thing happens here
// even though the "initializer" is an immediately invoked closure
var bar: SomeClass = {
    let b = SomeClass()
    b.someProperty = "whatever"
    b.doSomeStuff()
    return b
}()

// ditto for static properties in classes/structures/enums
class MyClass {
    static let singleton = MyClass()
    init() {
        print("foo")
    }
}

Итак, если вы используете dispatch_once для одноразовой инициализации, что приводит к некоторому значению - вы можете просто сделать это значение глобальной инициализацией или статическим свойством.

Но что, если вы используете dispatch_once для выполнения работы, которая не обязательно имеет результат? Вы все еще можете сделать это с помощью глобальной переменной или статического свойства: просто введите этот тип переменной Void:

let justAOneTimeThing: () = {
    print("Not coming back here.")
}()

И если доступ к глобальной переменной или статическому свойству для выполнения одноразовой работы просто вам не подходит - скажем, вы хотите, чтобы ваши клиенты вызывали функцию "инициализировать меня", прежде чем они будут работать с вашей библиотекой - просто оберните этот доступ в функцию:

func doTheOneTimeThing() {
    justAOneTimeThing
}

Подробнее см. руководство по миграции .

Ответ 2

Другие ответы здесь и вокруг interwebs довольно велики, но я чувствую, что этот маленький лакомый кусочек также следует упомянуть:

Самое замечательное в dispatch_once было то, насколько оно оптимизировано, по сути, приведение кода после первого запуска таким образом, что я почти не понимаю, но я уверен, что это будет намного быстрее, чем установка и проверка (реального) глобальный токен.

В то время как токена может быть разумно реализована в Swift, нужно объявить, что еще одно сохраненное логическое значение не так уж и велико. Не говоря уже о небезопасности. Как говорится в doc, вы должны использовать "лениво инициализированный глобальный". Да, но зачем загромождать глобальную сферу, верно?

Пока кто-то не убедит меня в более эффективном методе, я склонен объявить о своем закрытии в пределах области действия, в которой я буду использовать его или достаточно близко к нему, следующим образом:

private lazy var foo: Void = {
    // Do this once
}()

В основном я говорю, что "Когда я читаю это, foo должен быть результатом запуска этого блока". Он ведет себя точно так же, как глобальная константа let, как раз в правильной области. И красивее. Тогда я бы назвал это везде, где захочу, читая его во что-то, что никогда не будет использовано иначе. Мне нравится Swift _ для этого. Например:

_ = foo

Эта действительно крутая причуда на самом деле была вокруг, но не очень любила. Он в основном оставляет переменную только во время выполнения, как закрытое закрытие, пока что-то не захочет увидеть ее результат Void. При чтении он вызывает закрытие, отбрасывает его и сохраняет его результат foo. Void практически не использует память по-разному, поэтому последующие чтения (т.е. _ = foo) ничего не делают на процессоре. (Не цитируйте меня на этом, кто-нибудь, пожалуйста, проверьте сборку, чтобы быть уверенным!) Имейте как можно больше, и Swift в основном уходит, заботясь об этом после первого запуска! Потеряйте старый dispatch_once_t и сохраните свой код так же красиво, как когда вы впервые открыли его на Рождество!

Моя проблема заключается в том, что вы можете установить foo на что-то еще до его первого чтения, а затем ваш код никогда не будет вызываться! Следовательно, глобальная константа let, которая предотвращает это. Вещь в том, что константы в классе не работают хорошо с self, поэтому не играют с переменными экземпляра... Но если серьезно, когда вы все равно устанавливаете Void?

Это, и вам нужно будет указать тип возврата как Void или (), иначе он все равно будет жаловаться на self. Кто был хнык?

И lazy заключается в том, чтобы сделать переменную такой же ленивой, как и должно быть, поэтому Swift не запускает ее прямо на init().

Довольно шумный, до тех пор, пока вы помните, что не пишите!: P

Ответ 3

Пример для "dispatch_once" в Swift 3.0

Шаг 1: просто замените ниже код с помощью Singleton.swift(Singleton class)

// Singleton Class
class Singleton: NSObject { 
var strSample = NSString()

static let sharedInstance:Singleton = {
    let instance = Singleton ()
    return instance
} ()

// MARK: Init
 override init() {
    print("My Class Initialized")
    // initialized with variable or property
    strSample = "My String"
}
}

Пример изображения Singleton

Шаг 2: Вызовите Singleton из ViewController.swift

// ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let mySingleton = Singleton.sharedInstance
        print(mySingleton.strSample)

        mySingleton.strSample = "New String"

        print(mySingleton.strSample)

        let mySingleton1 = Singleton.sharedInstance
        print(mySingleton1.strSample)

    }

Образец просмотра ViewController

Вывод, подобный этому

My Class Initialized
My String
New String
New String

Ответ 4

Компиляция под Xcode 8 GA Swift 3

Рекомендуемый и элегантный способ создания экземпляра класса singleton dispatch_once:

final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...

Чтобы использовать его:

if TheRoot.shared.appState == .normal {
...
}

Что делают эти строки?

final - поэтому класс не может быть переопределен, расширен, он также делает код несколько быстрее для запуска, меньше ограничений.

static let shared = TheRoot() - эта строка выполняет ленивый init и работает только один раз.

Это решение является потокобезопасным.

Ответ 5

В то время как шаблон "lazy var" позволяет мне перестать заботиться о токенах отправки и, как правило, более удобен, чем dispatch_once(), мне не нравится, как он выглядит на сайте вызова:

_ = doSomethingOnce

Я бы ожидал, что этот оператор будет больше похож на вызов функции (так как он подразумевает действие), но он выглядит не так. Кроме того, необходимость писать _ = для явного отказа от результата не нужна и раздражает.

Существует лучший способ:

lazy var doSomethingOnce: () -> Void = {
  print("executed once")
  return {}
}()

Это делает возможным следующее:

doSomethingOnce()

Это может быть менее эффективным (поскольку он вызывает пустое закрытие вместо того, чтобы просто отбрасывать Void), но улучшенная ясность полностью стоит для меня.

Ответ 6

В соответствии с Руководство по миграции:

Бесплатная функция dispatch_once больше не доступна в Swift. В Swift, вы можете использовать лениво инициализированные глобальные или статические свойства и получить одинаковые гарантии безопасности потоков и позвонил один раз как dispatch_once при условии,.

Пример:

  let myGlobal = { … global contains initialization in a call to a closure … }()

  // using myGlobal will invoke the initialization code only the first time it is used.
  _ = myGlobal