Dispatch_once sau khi API Swift 3 GCD thay đổi


84

Cú pháp mới dispatch_oncetrong Swift sau những thay đổi được thực hiện trong phiên bản ngôn ngữ 3 là gì? Phiên bản cũ như sau.

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

Đây là những thay đổi đối với libdispatch đã được thực hiện.


Có thể có trùng lặp: Còn lại điều khiển nào trong Swift 3?
rickster

Dựa trên câu trả lời stackoverflow.com/a/38311178/1648724stackoverflow.com/a/39983813/1648724 , tôi đã tạo CocoaPod để thực hiện việc này: pod 'SwiftDispatchOnce', '~> 1.0'Chúc mừng. :]
JRG-Developer

Câu trả lời:


70

Từ tài liệu :

Dispatch
Chức năng miễn phí accept_once không còn khả dụng trong Swift. Trong Swift, bạn có thể sử dụng các hình cầu hoặc thuộc tính tĩnh được khởi tạo một cách lười biếng và nhận được các bảo đảm an toàn luồng và được gọi một lần giống như điều kiện đã cung cấp. Thí dụ:

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
Nó không phải là nếu bạn không biết rằng Swift sẽ thay đổi nhanh chóng và bạn sẽ phải sửa rất nhiều mã bị hỏng giữa các phiên bản Swift.
Abizern

2
nỗi đau lớn nhất là các pod của bên thứ 3 không phải lúc nào cũng tương thích với Swift3.
Tinkerbell

4
Đó là khoản nợ kỹ thuật mà bạn tích lũy khi giới thiệu phụ thuộc bên thứ ba, @Tinkerbell. Tôi yêu Swift nhưng rất thận trọng khi đưa ra các phụ thuộc bên ngoài sử dụng nó vì lý do này.
Chris Wagner

17
dispatch_onceđã rõ ràng. Này, không may, là xấu xí và khó hiểu ..
Alexandre G

104

Mặc dù việc sử dụng các hình cầu khởi tạo lười biếng có thể có ý nghĩa đối với việc khởi tạo một lần nào đó, nhưng nó không có ý nghĩa đối với các kiểu khác. Sẽ rất hợp lý khi sử dụng các hình cầu khởi tạo lười biếng cho những thứ như singleleton, nó không có ý nghĩa gì đối với những thứ như bảo vệ một thiết lập chóng mặt.

Đây là cách triển khai kiểu Swift 3 của accept_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()
    }
}

Đây là một ví dụ sử dụng:

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

hoặc sử dụng UUID

private let _onceToken = NSUUID().uuidString

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

Vì chúng ta hiện đang trong thời gian chuyển đổi từ nhanh 2 sang 3, đây là một ví dụ về triển khai nhanh 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()
    }

}

Cảm ơn bạn rất nhiều cho giải pháp. Chính xác là tôi đã bị mắc kẹt trong một thiết lập chóng mặt. Tôi hy vọng nhóm nhanh chóng giải quyết trường hợp sử dụng này.
salman140

2
Bạn hoàn toàn không nên sử dụng objc_sync_enterobjc_sync_exitnữa.
smat88dd

1
Và tại sao vậy?
Tod Cunningham

1
Để có hiệu suất, bạn nên sử dụng một tập hợp thay vì một mảng cho _onceTrackers. Điều này cải thiện độ phức tạp thời gian từ O (N) đến O (1).
Werner Altewischer

2
Vì vậy, bạn viết một lớp có thể tái sử dụng với giả định rằng nó sẽ không được sử dụng lại nhiều như vậy :-) Nếu nó không cần thêm nỗ lực nào để giảm độ phức tạp thời gian từ O (N) xuống O (1), bạn nên luôn làm điều đó IMHO.
Werner Altewischer

62

Mở rộng câu trả lời của Tod Cunningham ở trên, tôi đã thêm một phương pháp khác tạo mã thông báo tự động từ tệp, hàm và dòng.

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

Vì vậy, có thể đơn giản hơn để gọi:

DispatchQueue.once {
    setupUI()
}

và bạn vẫn có thể chỉ định một mã thông báo nếu bạn muốn:

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

Tôi cho rằng bạn có thể gặp sự cố nếu bạn có cùng một tệp trong hai mô-đun. Quá tệ là không có#module


điều này làm sáng tỏ thêm về những gì đang diễn ra. Cảm ơn.
nyxee


Thực sự đã giúp Thanx
Manjunath C.Kadani

19

Biên tập

Câu trả lời của @ Frizlab - giải pháp này không được đảm bảo là an toàn cho chuỗi. Một giải pháp thay thế nên được sử dụng nếu điều này là quan trọng

Giải pháp đơn giản là

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

    self.title = "Hello Lazy Guy"

    return
}()

được sử dụng như

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

1
Điều này không giúp ích gì cả, bởi vì một khai báo lazy var không thể được tạo nội tuyến với mã thông thường, nó phải nằm trong định nghĩa cấu trúc hoặc lớp. Điều đó có nghĩa là nội dung của sendOnce không thể nắm bắt được phạm vi xung quanh của một thể hiện. Ví dụ, nếu bạn khai báo một kết thúc mà không chạy được nêu ra, bạn có thể không sau đó khai báo struct bên trong đóng cửa đó và có nội dung var lười biếng của thể khác đóng cửa mà chụp vars từ việc đóng cửa xung quanh ...
CommaToast

3
Không đồng ý vì mã này chắc chắn không có cùng ngữ nghĩa với văn_phục_tạo. Dispatch_once đảm bảo mã được chạy chính xác một lần, cho dù bạn gọi nó từ chuỗi nào . Lazy vars có hành vi không xác định trong môi trường đa luồng.
Frizlab

trong init giải pháp này khối sẽ gọi hai lần trong một số trường hợp
thiêng liêng

8

Bạn vẫn có thể sử dụng nó nếu bạn thêm một tiêu đề bắc cầu:

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

Sau đó ở một .mnơi nào đó:

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

Bây giờ bạn có thể sử dụng mxcl_dispatch_once từ Swift.

Thay vào đó, bạn nên sử dụng những gì Apple đề xuất, nhưng tôi đã có một số mục đích sử dụng hợp pháp mà tôi cần dispatch_oncevới một mã thông báo duy nhất trong hai chức năng và thay vào đó, những gì Apple cung cấp không bao gồm.


7

Bạn có thể khai báo một hàm biến cấp cao nhất như sau:

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

sau đó gọi điều này ở bất cứ đâu:

doOnce()

1
Lazy vars có phạm vi trong lớp, vì vậy điều này hoàn toàn sẽ không hoạt động giống như cử_once. Nó sẽ thực thi một lần cho mỗi phiên bản của lớp bên dưới. Di chuyển nó ra ngoài lớp [private var doOnce: () -> () = {}] hoặc đánh dấu nó là static [static private var doOnce: () -> () = {}]
Eli Burke

1
Chuẩn xác! Cảm ơn. Trong hầu hết các trường hợp, bạn sẽ cần một lần hành động cho mỗi trường hợp.
Bogdan Novikov

2
Đây là một giải pháp thực sự tuyệt vời! Elegant, ngắn, và rõ ràng
Bến Leggiero

6

Swift 3: Dành cho những người thích các lớp (hoặc cấu trúc) có thể tái sử dụng:

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

Sử dụng:

class MyViewController: UIViewController {

   private let /* var */ setUpOnce = DispatchOnce()

   override func viewWillAppear() {
      super.viewWillAppear()
      setUpOnce.perform {
         // Do some work here
         // ...
      }
   }

}

Bản cập nhật (ngày 28 tháng 4 năm 2017): được OSSpinLockthay thế bằng os_unfair_lockcảnh báo hết hạn sử dụng trong 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)
   }
}

Tôi nhận được một thông điệp rằng OSSSpinLock bị phản đối trong iOS 10.0
markhorrocks

2
Cảm ơn! Đã cập nhật mã mẫu. OSSpinLockthay thế bằng os_unfair_lock. BTW: Đây là một video WWDC hay về Concurrent Programming: developer.apple.com/videos/play/wwdc2016/720
Vlad

0

Tôi cải thiện câu trả lời ở trên nhận được kết quả:

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

Sử dụng phương pháp tiếp cận hằng số lớp nếu bạn đang sử dụng Swift 1.2 trở lên và phương pháp tiếp cận cấu trúc lồng nhau nếu bạn cần hỗ trợ các phiên bản trước đó. Khám phá mô hình Singleton trong Swift. Tất cả các phương pháp bên dưới đều hỗ trợ khởi tạo lười biếng và an toàn luồng. Phương pháp tiếp cận accept_once không hoạt động trong Swift 3.0

Phương pháp tiếp cận A: Hằng số lớp

class SingletonA {

    static let sharedInstance = SingletonA()

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

}

Phương pháp tiếp cận B: Cấu trúc lồng nhau

class SingletonB {

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

}

Phương pháp tiếp cận C: accept_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
Câu hỏi được hỏi cụ thể về giải pháp cho Swift 3.
thesummersign
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.