dispatch_once після змін Swift 3 GCD API


84

Для чого новий синтаксис dispatch_onceу Swift після змін, внесених до мовної версії 3? Стара версія була такою.

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

Це внесені зміни до libdispatch .


Можливий дублікат: Куди dispatch_once у Swift 3?
Rrickster

На основі відповідей stackoverflow.com/a/38311178/1648724 та stackoverflow.com/a/39983813/1648724 , я створив CocoaPod для цього: pod 'SwiftDispatchOnce', '~> 1.0'Cheers. :]
JRG-розробник

Відповіді:


70

З документа :

Відправка
Безкоштовна функція 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.

3
Це не так, якби ви не знали, що Swift буде швидко змінюватися, і вам доведеться виправляти багато зламаних кодів між версіями Swift.
Abizern

2
найбільший біль - це сторонні стручки, які не завжди сумісні з Swift3.
Тінкербелл

4
Це технічний борг, який ви накопичуєте при введенні залежностей від сторонніх розробників, @Tinkerbell. Я люблю Свіфта, але я дуже обережно залучаю зовнішні залежності, які використовують його саме з цієї причини.
Кріс Вагнер,

17
dispatch_onceбуло зрозуміло. Це, на жаль, потворно і заплутано ..
Alexandre G

104

Хоча використання ледачих ініціалізованих глобалів може мати сенс для певної разової ініціалізації, для інших типів це не має сенсу. Дуже має сенс використовувати ледачі ініціалізовані глобалі для таких речей, як одиночні, не має сенсу для таких речей, як охорона налаштувань swizzle.

Ось реалізація стилю Swift 3 dispatch_once:

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!" )
}

Оскільки ми зараз перебуваємо в момент переходу від швидкого 2 до 3, ось приклад швидкого здійснення 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()
    }

}

Щиро дякую за рішення. Я точно потрапляв у пастку в налаштуваннях для swizzle. Я сподіваюся, що швидка команда вирішить цей випадок використання.
salman140

2
Ви абсолютно не повинні використовувати objc_sync_enterі objc_sync_exitбільше.
smat88dd

1
І чому це?
Тод Каннінгем,

1
Для продуктивності слід використовувати набір замість масиву для _onceTrackers. Це покращує часову складність від O (N) до O (1).
Вернер Альтевішер

2
Отже, ви пишете багаторазовий клас, припускаючи, що він не буде повторно використаний так сильно :-) Якщо для цього не потрібно додаткових зусиль для зниження часової складності з O (N) до O (1), ви завжди повинні робити це за допомогою IMHO.
Вернер Альтевішер

62

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

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

Дійсно допомогло Thanx
Manjunath C.Kadani

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

у цьому рішенні init block в деяких випадках буде дзвонити двічі
священне

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.


7

Ви можете оголосити функцію змінної верхнього рівня таким чином:

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

тоді зателефонуйте це де завгодно

doOnce()

1
Ледачі вари прицілюються до класу, тому це абсолютно не буде діяти як dispatch_once. Він буде виконуватися один раз на екземпляр базового класу. Або перемістіть його за межі класу [private var doOnce: () -> () = {}], або позначте його статичним [static private var doOnce: () -> () = {}]
Eli Burke

1
Абсолютно правильно! Дякую. У більшості випадків вам знадобляться один раз дії на екземпляр.
Богдан Новіков

2
Це справді чудове рішення! Елегантний, короткий та чіткий
Бен Леггієро,

6

Свіфт 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. BTW: Ось гарне відео про 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()
        }
    }
}

-3

Використовуйте підхід до константи класу, якщо ви використовуєте Swift 1.2 або новішу версію, та вкладений підхід структури, якщо вам потрібна підтримка попередніх версій. Дослідження шаблону Сінглтона в Свіфті. Усі наведені нижче підходи підтримують ледачу ініціалізацію та безпеку потоків. dispatch_once не працює в Swift 3.0

Підхід А: Постійна класу

class SingletonA {

    static let sharedInstance = SingletonA()

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

}

Підхід Б: Вкладена структура

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.