Swift 3.0之后实现Dispatch once扩展

作者: Inlight先森 | 来源:发表于2017-11-10 12:05 被阅读526次

在Swift 3.0中原有的Dispatch once已经被废弃了,这种写法已经不再被支持了

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

文档说明:

Dispatch
The free function dispatch_once is no longer available in Swift. In Swift, you can use lazily initialized globals or static properties and get the same thread-safety and called-once guarantees as dispatch_once provided. Example:

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 提供的线程安全的实现方案,但是有些时候使用这种方式意义不大。

我们可以通过给DispatchQueue实现一个扩展方法来实现原有的功能:

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()
    }
}

通过使用 token 作为唯一标识 执行 once 方法时通过加锁避免多线程下的 token 值不确定的情况。像这样调用:

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

或者使用UUID

private let _onceToken = NSUUID().uuidString

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

当然如果觉得每次使用 once 方法都要想一个不同的 token 有些伤脑可以使用下面的方式:

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()
}

也支持自定义 token 的使用方式:

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

每次自动生成一个类似 "/Users/iOS/Demo/ViewController.swift:viewWillAppear:136"这样的 token 避免每次都要费尽脑汁定义一个 token,看起来也更直观。
注意:如果在两个模块中有同名的文件并且使用 once 之处在同一个方法的相同行处,可能会发生冲突。这种情况虽然概率很低但也是有可能发生的,若出现此情况请定义唯一的 token,使用传递参数这种方式来实现。

相关链接

相关文章

网友评论

    本文标题:Swift 3.0之后实现Dispatch once扩展

    本文链接:https://www.haomeiwen.com/subject/ogxbmxtx.html