2016-06-17 32 views
56

在語言版本3中進行更改後,Swift中dispatch_once的新語法是什麼?舊版本如下。Swatch 3 GCD API更改後的dispatch_once

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

這些製成的are the changes to libdispatch

+0

可能重複:[DISP何去何從atch_once in Swift 3?](http://stackoverflow.com/q/37801407/957768) – rickster

+0

根據回答https://stackoverflow.com/a/38311178/1648724和https://stackoverflow.com/a/ 39983813/1648724,我創建了一個CocoaPod來執行此操作:['pod'SwiftDispatchOnce','〜> 1.0''](https://github.com/JRG-Developer/SwiftDispatchOnce)乾杯。 :] –

回答

38

doc

調度
自由功能dispatch_once不再提供 斯威夫特。在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. 
+2

這不像你不知道Swift會快速變化,你將不得不糾正Swift版本之間的許多破碎的代碼。 – Abizern

+2

最大的痛苦就是第三方的Pod並不總是與Swift3兼容。 – Tinkerbell

+4

這就是您引入第三方依賴關係時引發的技術債務@Tinkerbell。我喜歡Swift,但是爲了這個原因,我們特別謹慎地引入了使用它的外部依賴。 –

72

在使用延遲初始化的全局變量可以使一些一次性初始化意義上說,它並不適用於其他類型的意義。對於像singleton這樣的事情,使用懶惰的初始化全局變量是很有意義的,但對於守護swizzle設置等事情來說,這並沒有太多意義。

這裏是一個斯威夫特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!") 
} 

正如我們目前在從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() 
    } 

} 
+0

非常感謝您的解決方案。我正被困在一個swizzle設置中。我希望迅速的團隊能夠解決這個用例。 – salman140

+0

你絕對不應該使用'objc_sync_enter'和'objc_sync_exit'了。 – smat88dd

+0

爲什麼呢? –

35

在上面的Tod Cunningham的回答中進行了擴展,我添加了另一種從文件,函數和行中自動生成令牌的方法。

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

    public class func once(file: String = #file, function: String = #function, line: Int = #line, block:(Void)->Void) { 
     let token = file + ":" + function + ":" + String(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)->Void) { 
     objc_sync_enter(self) 
     defer { objc_sync_exit(self) } 


     if _onceTracker.contains(token) { 
      return 
     } 

     _onceTracker.append(token) 
     block() 
    } 
} 

所以可以簡單稱之爲:

DispatchQueue.once { 
    setupUI() 
} 

,你還可以指定一個令牌,如果你想:

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

我想你可以得到一個碰撞,如果你有兩個模塊中的相同文件。太糟糕了,沒有#module

+0

真的很有幫助,謝謝 – Svitlana

+0

這讓我們更清楚地瞭解正在發生的事情。謝謝。 – nyxee

5

你仍然可以使用它,如果你添加一個橋接報:在某處.m

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

然後:

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

您現在應該能夠使用來自Swift的mxcl_dispatch_once

大多數情況下,您應該使用蘋果的建議,但我有一些合法的用途,我需要在兩個函數中使用單個令牌來代替dispatch_once,並且Apple沒有提供什麼內容。

-1

如果您使用的是Swift 1.2或更高版本,並且需要支持早期版本的嵌套結構方法,請使用類常量方法。 Swift中單例模式的探索。下面的所有方法都支持延遲初始化和線程安全。類恆定 類SingletonA {

static let sharedInstance = SingletonA() 

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

} 方法B::嵌套結構 類SingletonB {

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

} 方法 dispatch_once方法不是在迅速3.0

視角下的工作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! 
} 

}

+0

具體詢問Swift 3解決方案的問題。 – thesummersign

5

斯威夫特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) 
    } 
} 
+0

我收到OSSSpinLock在iOS 10.0中棄用的消息 – markhorrocks

+2

謝謝!示例代碼已更新。用'os_unfair_lock'替換'OSSpinLock'。 BTW:這是一個關於'Concurrent Programming'的WWDC視頻:https://developer.apple.com/videos/play/wwdc2016/720/ – Vlad

8

簡單的解決方案是使用像

override func viewDidLayoutSubviews() { 
    super.viewDidLayoutSubviews() 
    _ = dispatchOnce 
} 
+0

這完全沒有幫助,因爲懶惰的var聲明不能內聯使用常規代碼,它必須位於結構或類定義中。這意味着dispatchOnce的內容無法捕獲實例的周圍範圍。例如,如果聲明一個尚未運行的閉包,則不能在該閉包中聲明該結構,並將該懶惰的var的內容作爲另一個閉包,以捕獲周圍閉包中的變量... – CommaToast

+1

Downvoted,因爲此代碼肯定是**不是與dispatch_once相同的語義。 dispatch_once確保代碼只運行一次,**從**調用它的任何線程。懶惰變量在多線程環境中具有未定義的行爲。 – Frizlab

-3
I have created below function 

func executeOnce(code: @escaping() -> Void) 
     { 
       if UserDefaults.standard.value(forKey: "3333##112233") == nil 
       { 
        code() 
        UserDefaults.standard.setValue("vv", forKey: "3333##112233") 
        UserDefaults.standard.synchronize() 
       } 
     } 

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

    self.title = "Hello Lazy Guy" 

    return 
}() 

,並使用如下

executeOnce { 

     print("onces") 
    }