Куда отправляется в 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.")
}
}
7 ответов
Начиная с 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.")
}()
И если доступ к глобальной переменной или статическому свойству для выполнения одноразовой работы просто не подходит вам - скажем, вы хотите, чтобы ваши клиенты вызывали функцию "initialize me" до того, как они будут работать с вашей библиотекой - просто оберните это доступ в функции:
func doTheOneTimeThing() {
justAOneTimeThing
}
Смотрите руководство по миграции для получения дополнительной информации.
В то время как шаблон "lazy var" позволяет мне перестать заботиться о токенах рассылки и, как правило, более удобен, чем dispatch_once()
было, мне не нравится, как это выглядит на сайте вызова:
_ = doSomethingOnce
Я ожидал бы, что это утверждение будет больше похоже на вызов функции (так как оно подразумевает действие), но это совсем не так. Также приходится писать _ =
явный отказ от результата не нужен и раздражает.
Существует лучший способ:
lazy var doSomethingOnce: () -> Void = {
print("executed once")
return {}
}()
Что делает возможным следующее:
doSomethingOnce()
Это может быть менее эффективно (поскольку вызывает пустое замыкание, а не просто отбрасывает Void
), но улучшенная ясность полностью стоит для меня.
Другие ответы здесь и вокруг паутин довольно хороши, но я чувствую, что этот маленький кусочек также следует упомянуть:
Отличная вещь о dispatch_once
было то, насколько он был оптимизирован, по сути, отсоединение кода после первого запуска способом, который я едва понимаю, но разумно уверен, что это будет намного быстрее, чем установка и проверка (реального) глобального токена.
В то время как токен может быть разумно реализован в Swift, необходимость объявления еще одного сохраненного логического значения не так уж велика. Не говоря уже о небезопасных. Как сказано в документе, вы должны использовать "лениво инициализированный глобальный". Да, но зачем загромождать глобальный охват, верно?
Пока кто-нибудь не убедит меня в лучшем методе, я, как правило, объявляю свое закрывающее действие "один раз" внутри области, в которой я буду его использовать, или достаточно близко к нему, следующим образом:
private lazy var foo: Void = {
// Do this once
}()
В основном я говорю, что "Когда я читаю это, foo
должен быть результатом запуска этого блока. "Он ведет себя точно так же, как глобальный let
константа, просто в нужном объеме. И красивее. Тогда я назвал бы это, где бы я ни хотел, читая это в то, что никогда не будет использоваться иначе. Мне нравится свифт _
для этого. Вот так:
_ = foo
Эта действительно крутая причуда была уже давно, но не встречала особой любви. Он в основном оставляет переменную одну во время выполнения, как невостребованное закрытие, пока что-то не захочет увидеть ее Void
результат. При чтении он вызывает замыкание, выбрасывает его и сохраняет результат в foo
, Void
практически ничего не использует в памяти, поэтому последующее чтение (т.е. _ = foo
) ничего не делать на процессоре. (Не цитируйте меня об этом, кто-нибудь, пожалуйста, проверьте сборку, чтобы быть уверенным!) Имейте столько, сколько хотите, и Swift в основном перестает заботиться об этом после первого запуска! Потерять это старое dispatch_once_t
и сохраняйте большую часть своего кода такой же красивой, как когда вы впервые открыли его на Рождество!
Моя единственная проблема в том, что вы можете установить foo
к чему-то еще до его первого чтения, и тогда ваш код никогда не будет вызван! Отсюда глобальный let
постоянная, которая предотвращает это. Дело в том, что константы в области видимости не очень хорошо self
так что не играйте с переменными экземпляра... А если серьезно, когда вы устанавливаете что-либо в Void
тем не мение??
Это, и вам нужно будет указать тип возвращаемого значения как Void
или же ()
иначе еще будет жаловаться self
, Кто такой?
А также lazy
просто сделать переменную такой ленивой, какой должна быть, чтобы Swift не запускал ее init()
,
Довольно шикарно, если ты помнишь, чтобы не писать в него!:П
Пример для "dispatch_once" в Swift 3.0
Шаг 1: Просто замените приведенный ниже код на ваш Singleton.swift (класс Singleton)
// 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"
}
}
Шаг 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
Компилирует под Xcode 8 GA Swift 3
Рекомендуемый и элегантный способ создания экземпляра одноэлементного класса dispatch_once:
final class TheRoot {
static let shared = TheRoot()
var appState : AppState = .normal
...
Чтобы использовать это:
if TheRoot.shared.appState == .normal {
...
}
Что делают эти строки?
final - так что класс не может быть переопределен, расширен, он также делает код несколько более быстрым для выполнения, меньше косвенных ссылок.
static let shared = TheRoot () - эта строка выполняет отложенную инициализацию и выполняется только один раз.
Это решение является потокобезопасным.
Согласно Руководству по миграции:
Бесплатная функция 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
Потокобезопасный dispatch_once:
private lazy var foo: Void = {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
// Do this once
}()