Куда отправляется в 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
}()
Другие вопросы по тегам