dispatch_once после изменений API Swift 3 GCD

85

Каков новый синтаксис dispatch_onceSwift после изменений, внесенных в языковую версию 3? Старая версия была следующей.

var token: dispatch_once_t = 0
func test() {
    dispatch_once(&token) {
    }
}

Это внесенные изменения в libdispatch .

Дэвид
источник
Возможный дубликат: куда dispatch_once в Swift 3?
rickster
Основываясь на ответах stackoverflow.com/a/38311178/1648724 и stackoverflow.com/a/39983813/1648724 , я создал CocoaPod для этого: pod 'SwiftDispatchOnce', '~> 1.0'Ура. :]
JRG-Developer

Ответы:

70

Из документа :

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

let myGlobal: () = { … global contains initialization in a call to a closure … }()
_ = myGlobal  // using myGlobal will invoke the initialization code only the first time it is used.
Дершовиц123
источник
3
Это не значит, что вы не знали, что Swift будет быстро меняться, и вам придется исправлять много неработающего кода между версиями Swift.
Abizern
2
Самая большая проблема - это модули сторонних производителей, которые не всегда совместимы с Swift3.
Тинкербелл,
4
Это технический долг, который вы накапливаете при внедрении сторонних зависимостей, @Tinkerbell. Мне нравится Swift, но я очень осторожен, добавляя внешние зависимости, которые используют его именно по этой причине.
Крис Вагнер,
17
dispatch_onceбыло ясно. Это, к сожалению, некрасиво и сбивает с толку ..
Alexandre G
105

Хотя использование ленивых инициализированных глобальных объектов может иметь смысл для некоторой однократной инициализации, это не имеет смысла для других типов. Имеет большой смысл использовать ленивые инициализированные глобальные переменные для таких вещей, как синглтоны, это не имеет большого смысла для таких вещей, как защита настройки swizzle.

Вот реализация dispatch_once в стиле Swift 3:

public extension DispatchQueue {

    private static var _onceTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String, block:@noescape(Void)->Void) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}

Вот пример использования:

DispatchQueue.once(token: "com.vectorform.test") {
    print( "Do This Once!" )
}

или используя UUID

private let _onceToken = NSUUID().uuidString

DispatchQueue.once(token: _onceToken) {
    print( "Do This Once!" )
}

Поскольку в настоящее время мы находимся во время перехода от Swift 2 к 3, вот пример реализации Swift 2:

public class Dispatch
{
    private static var _onceTokenTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token token: String, @noescape block:dispatch_block_t) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTokenTracker.contains(token) {
            return
        }

        _onceTokenTracker.append(token)
        block()
    }

}
Тод Каннингем
источник
Большое спасибо за решение. Я как раз попал в ловушку свази. Я надеюсь, что команда Swift рассмотрит этот вариант использования.
salman140
2
Вам ни в коем случае нельзя использовать objc_sync_enterи objc_sync_exitбольше.
smat88dd 08
1
И почему так?
Тод Каннингем
1
Для повышения производительности вы должны использовать набор вместо массива для _onceTrackers. Это улучшает временную сложность с O (N) до O (1).
Вернер Альтевишер
2
Итак, вы пишете многоразовый класс, предполагая, что он не будет использоваться повторно :-) Если не требуется дополнительных усилий для снижения временной сложности с O (N) до O (1), вы всегда должны делать это IMHO.
Вернер Альтевишер
64

Расширяя ответ Тода Каннингема выше, я добавил еще один метод, который автоматически делает токен из файла, функции и строки.

public extension DispatchQueue {
    private static var _onceTracker = [String]()

    public class func once(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String,
                           block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }
}

Так что проще позвонить:

DispatchQueue.once {
    setupUI()
}

и вы все равно можете указать токен, если хотите:

DispatchQueue.once(token: "com.hostname.project") {
    setupUI()
}

Я полагаю, вы можете столкнуться с коллизией, если у вас есть один и тот же файл в двух модулях. Жаль, что нет#module

Волк
источник
это проливает свет на то, что происходит. Благодарю.
nyxee
Ссылка на код выше: gitlab.com/zakkhoyt/DispatchOnceExample/raw/master/…
VaporwareWolf
Действительно помог Тханкс
Манджунатх Кадани
но если я понимаю, ваш код будет выполнен только один раз, даже если класс будет удален и воссоздан, верно? он отличается от оригинального dispatch_once, если я правильно помню ... не так ли?
yonivav
1
@yonivav Да, вы правы. Чтобы обойти это, вы также можете передать адрес памяти объекта вместе с файлом, строкой и т. Д. Сделайте адрес частью токена.
VaporwareWolf
19

редактировать

Ответ @Frizlab - это решение не гарантирует многопоточность. Если это важно, следует использовать альтернативу.

Простое решение

lazy var dispatchOnce : Void  = { // or anyName I choose

    self.title = "Hello Lazy Guy"

    return
}()

используется как

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    _ = dispatchOnce
}
Райан Хайтнер
источник
1
Это совершенно не помогает, потому что ленивое объявление var не может быть встроено в обычный код, оно должно быть в определении структуры или класса. Это означает, что содержимое dispatchOnce не может захватывать окружающую область действия экземпляра. Например, если вы объявляете закрытие, которое еще не было запущено, вы не можете затем объявить структуру внутри этого закрытия и сделать так, чтобы содержимое ленивого var было еще одним закрытием, которое захватывает переменные из окружающего закрытия ...
CommaToast
3
Проголосовали против, потому что этот код определенно не имеет той же семантики, что и dispatch_once. dispatch_once гарантирует, что код будет запущен ровно один раз, из какого бы потока вы его ни вызывали . Ленивые вары имеют неопределенное поведение в многопоточной среде.
Frizlab
в этом решении блок инициализации в некоторых случаях будет вызывать дважды
священный
8

Вы все еще можете использовать его, если добавите заголовок моста:

typedef dispatch_once_t mxcl_dispatch_once_t;
void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block);

Затем .mгде-нибудь:

void mxcl_dispatch_once(mxcl_dispatch_once_t *predicate, dispatch_block_t block) {
    dispatch_once(predicate, block);
}

Теперь вы можете использовать mxcl_dispatch_onceSwift.

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

mxcl
источник
8

Вы можете объявить функцию переменной верхнего уровня следующим образом:

private var doOnce: ()->() = {
    /* do some work only once per instance */
    return {}
}()

затем вызовите это где угодно:

doOnce()
Богдан Новиков
источник
1
Ленивые вары привязаны к классу, поэтому это абсолютно не будет действовать как dispatch_once. Он будет выполняться один раз для каждого экземпляра базового класса. Либо переместите его за пределы класса [private var doOnce: () -> () = {}], или отметьте его статическим [static private var doOnce: () -> () = {}]
Эли Берк
1
Абсолютно правильно! Благодарю. В большинстве случаев вам понадобится одно действие для каждого экземпляра.
Богдан Новиков
2
Это действительно отличное решение! Элегантный, короткий и ясный
Бен Легжеро
6

Swift 3: для тех, кто любит многоразовые классы (или структуры):

public final class /* struct */ DispatchOnce {
   private var lock: OSSpinLock = OS_SPINLOCK_INIT
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      OSSpinLockLock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      OSSpinLockUnlock(&lock)
   }
}

Применение:

class MyViewController: UIViewController {

   private let /* var */ setUpOnce = DispatchOnce()

   override func viewWillAppear() {
      super.viewWillAppear()
      setUpOnce.perform {
         // Do some work here
         // ...
      }
   }

}

Обновление (28 апреля 2017 г.): OSSpinLockзаменено os_unfair_lockпредупреждениями об устаревании в macOS SDK 10.12.

public final class /* struct */ DispatchOnce {
   private var lock = os_unfair_lock()
   private var isInitialized = false
   public /* mutating */ func perform(block: (Void) -> Void) {
      os_unfair_lock_lock(&lock)
      if !isInitialized {
         block()
         isInitialized = true
      }
      os_unfair_lock_unlock(&lock)
   }
}
Влад
источник
Я получаю сообщение о том, что OSSSpinLock устарел в iOS 10.0
markhorrocks
2
Благодаря! Обновлен пример кода. OSSpinLockзаменен на os_unfair_lock. Кстати: Вот хорошее видео WWDC о Concurrent Programming: developer.apple.com/videos/play/wwdc2016/720
Влад
0

Я улучшаю приведенные выше ответы, получаю результат:

import Foundation
extension DispatchQueue {
    private static var _onceTracker = [AnyHashable]()

    ///only excute once in same file&&func&&line
    public class func onceInLocation(file: String = #file,
                           function: String = #function,
                           line: Int = #line,
                           block: () -> Void) {
        let token = "\(file):\(function):\(line)"
        once(token: token, block: block)
    }

    ///only excute once in same Variable
    public class func onceInVariable(variable:NSObject, block: () -> Void){
        once(token: variable.rawPointer, block: block)
    }
    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: AnyHashable,block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }

        guard !_onceTracker.contains(token) else { return }

        _onceTracker.append(token)
        block()
    }

}

extension NSObject {
    public var rawPointer:UnsafeMutableRawPointer? {
        get {
            Unmanaged.passUnretained(self).toOpaque()
        }
    }
}
leo.tan
источник
Добавьте пример о том, как его использовать.
Satyam
-3

Используйте подход констант класса, если вы используете Swift 1.2 или выше, и подход вложенной структуры, если вам нужно поддерживать более ранние версии. Исследование паттерна Синглтон в Swift. Все приведенные ниже подходы поддерживают отложенную инициализацию и безопасность потоков. Подход dispatch_once не работает в Swift 3.0

Подход A: константа класса

class SingletonA {

    static let sharedInstance = SingletonA()

    init() {
        println("AAA");
    }

}

Подход B: вложенная структура

class SingletonB {

    class var sharedInstance: SingletonB {
        struct Static {
            static let instance: SingletonB = SingletonB()
        }
        return Static.instance
    }

}

Подход C: dispatch_once

class SingletonC {

    class var sharedInstance: SingletonC {
        struct Static {
            static var onceToken: dispatch_once_t = 0
            static var instance: SingletonC? = nil
        }
        dispatch_once(&Static.onceToken) {
            Static.instance = SingletonC()
        }
        return Static.instance!
    }
}
Шан Шафик
источник
1
Вопрос, в котором конкретно задан вопрос о решении для Swift 3.
thesummersign 02