Для чого новий синтаксис dispatch_once
у Swift після змін, внесених до мовної версії 3? Стара версія була такою.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
Для чого новий синтаксис dispatch_once
у Swift після змін, внесених до мовної версії 3? Стара версія була такою.
var token: dispatch_once_t = 0
func test() {
dispatch_once(&token) {
}
}
pod 'SwiftDispatchOnce', '~> 1.0'
Cheers. :]
Відповіді:
З документа :
Відправка
Безкоштовна функція 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.
dispatch_once
було зрозуміло. Це, на жаль, потворно і заплутано ..
Хоча використання ледачих ініціалізованих глобалів може мати сенс для певної разової ініціалізації, для інших типів це не має сенсу. Дуже має сенс використовувати ледачі ініціалізовані глобалі для таких речей, як одиночні, не має сенсу для таких речей, як охорона налаштувань 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()
}
}
objc_sync_enter
і objc_sync_exit
більше.
Розширюючи відповідь Тода Каннінгема вище, я додав ще один метод, який автоматично робить маркер з файлу, функції та рядка.
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
Редагувати
Просте рішення
lazy var dispatchOnce : Void = { // or anyName I choose
self.title = "Hello Lazy Guy"
return
}()
використовується як
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
_ = dispatchOnce
}
Ви все ще можете використовувати його, якщо додасте заголовок переходу:
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_once
Swift.
Здебільшого ви повинні використовувати те, що пропонує Apple, замість цього, але я мав кілька законних застосувань, де мені потрібно було dispatch_once
використовувати один маркер у двох функціях, і на це не поширюється те, що пропонує Apple.
Ви можете оголосити функцію змінної верхнього рівня таким чином:
private var doOnce: ()->() = {
/* do some work only once per instance */
return {}
}()
тоді зателефонуйте це де завгодно
doOnce()
Свіфт 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)
}
}
OSSpinLock
замінено на os_unfair_lock
. BTW: Ось гарне відео про WWDC про Concurrent Programming
: developer.apple.com/videos/play/wwdc2016/720
Я вдосконалюю відповіді вище, отримую результат:
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()
}
}
}
Використовуйте підхід до константи класу, якщо ви використовуєте 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!
}
}