Куда 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.")
}
}
Ответы
Ответ 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