Jaka jest nowa składnia dispatch_once
w Swift po zmianach wprowadzonych w wersji językowej 3? Stara wersja wyglądała następująco.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
Jaka jest nowa składnia dispatch_once
w Swift po zmianach wprowadzonych w wersji językowej 3? Stara wersja wyglądała następująco.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
pod 'SwiftDispatchOnce', '~> 1.0'
Pozdrawiam. :]Odpowiedzi:
Z dokumentu :
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.
źródło
dispatch_once
był czysty. To niestety jest brzydkie i zagmatwane…Chociaż używanie leniwych zainicjowanych globali może mieć sens w przypadku jednorazowej inicjalizacji, nie ma sensu w przypadku innych typów. Używanie leniwych zainicjowanych globali do rzeczy takich jak singletony ma sens, nie ma to większego sensu w przypadku rzeczy takich jak ochrona konfiguracji swizzle.
Oto implementacja metody dispatch_once w stylu 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() } }
Oto przykład użycia:
DispatchQueue.once(token: "com.vectorform.test") { print( "Do This Once!" ) }
lub używając UUID
private let _onceToken = NSUUID().uuidString DispatchQueue.once(token: _onceToken) { print( "Do This Once!" ) }
Ponieważ jesteśmy obecnie w okresie przechodzenia od swift 2 do 3, oto przykład implementacji 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() } }
źródło
objc_sync_enter
iobjc_sync_exit
więcej.Rozszerzając powyższą odpowiedź Toda Cunninghama, dodałem inną metodę, która automatycznie tworzy token z pliku, funkcji i wiersza.
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() } }
Więc może być prościej zadzwonić:
DispatchQueue.once { setupUI() }
i nadal możesz określić token, jeśli chcesz:
DispatchQueue.once(token: "com.hostname.project") { setupUI() }
Przypuszczam, że możesz mieć kolizję, jeśli masz ten sam plik w dwóch modułach. Szkoda, że nie ma
#module
źródło
Edytować
Odpowiedź @ Frizlab - nie ma gwarancji, że to rozwiązanie będzie bezpieczne dla wątków. Jeśli ma to kluczowe znaczenie, należy skorzystać z alternatywy
Proste rozwiązanie to
lazy var dispatchOnce : Void = { // or anyName I choose self.title = "Hello Lazy Guy" return }()
używany jak
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() _ = dispatchOnce }
źródło
Nadal możesz go używać, jeśli dodasz nagłówek mostkujący:
Następnie
.m
gdzieś:Powinieneś teraz móc używać
mxcl_dispatch_once
z poziomu Swift.Przede wszystkim powinieneś użyć tego, co sugeruje Apple, ale miałem kilka uzasadnionych zastosowań, w których musiałem,
dispatch_once
z jednym tokenem w dwóch funkcjach, a zamiast tego nie jest to objęte tym, co zapewnia Apple.źródło
Możesz zadeklarować funkcję zmiennej najwyższego poziomu w następujący sposób:
private var doOnce: ()->() = { /* do some work only once per instance */ return {} }()
następnie zadzwoń do tego gdziekolwiek:
źródło
Swift 3: Dla tych, którzy lubią klasy (lub struktury) wielokrotnego użytku:
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) } }
Stosowanie:
class MyViewController: UIViewController { private let /* var */ setUpOnce = DispatchOnce() override func viewWillAppear() { super.viewWillAppear() setUpOnce.perform { // Do some work here // ... } } }
Aktualizacja (28 kwietnia 2017 r.):
OSSpinLock
Zastąpionoos_unfair_lock
ostrzeżeniami o stosownym wycofaniu w zestawie 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) } }
źródło
OSSpinLock
zastąpionyos_unfair_lock
. BTW: Oto dobry film WWDC oConcurrent Programming
: developer.apple.com/videos/play/wwdc2016/720Poprawiam powyższe odpowiedzi otrzymuję wynik:
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() } } }
źródło
Użyj metody stałej klasy, jeśli używasz Swift 1.2 lub nowszego, a zagnieżdżonej struktury, jeśli potrzebujesz obsługiwać wcześniejsze wersje. Eksploracja wzoru Singleton w Swift. Wszystkie poniższe podejścia obsługują leniwą inicjalizację i bezpieczeństwo wątków. Podejście dispatch_once nie działa w Swift 3.0
Podejście A: stała klasy
class SingletonA { static let sharedInstance = SingletonA() init() { println("AAA"); } }
Podejście B: struktura zagnieżdżona
class SingletonB { class var sharedInstance: SingletonB { struct Static { static let instance: SingletonB = SingletonB() } return Static.instance } }
Podejście 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! } }
źródło