Kaçan parametrenin kapatılması, kaçmasına izin verebilir


139

Bir protokol var:

enum DataFetchResult {
    case success(data: Data)
    case failure
}

protocol DataServiceType {
    func fetchData(location: String, completion: (DataFetchResult) -> (Void))
    func cachedData(location: String) -> Data?
}

Örnek bir uygulama ile:

    /// An implementation of DataServiceType protocol returning predefined results using arbitrary queue for asynchronyous mechanisms.
    /// Dedicated to be used in various tests (Unit Tests).
    class DataMockService: DataServiceType {

        var result      : DataFetchResult
        var async       : Bool = true
        var queue       : DispatchQueue = DispatchQueue.global(qos: .background)
        var cachedData  : Data? = nil

        init(result : DataFetchResult) {
            self.result = result
        }

        func cachedData(location: String) -> Data? {
            switch self.result {
            case .success(let data):
                return data
            default:
                return nil
            }
        }

        func fetchData(location: String, completion: (DataFetchResult) -> (Void)) {

            // Returning result on arbitrary queue should be tested,
            // so we can check if client can work with any (even worse) implementation:

            if async == true {
                queue.async { [weak self ] in
                    guard let weakSelf = self else { return }

                    // This line produces compiler error: 
                    // "Closure use of non-escaping parameter 'completion' may allow it to escape"
                    completion(weakSelf.result)
                }
            } else {
               completion(self.result)
            }
        }
    }

Yukarıdaki kod Swift3'te (Xcode8-beta5) derlenmiş ve çalışmıştır, ancak artık beta 6 ile çalışmaz. Beni altta yatan nedene işaret edebilir misiniz?


5
Swift 3
Honey

1
Bunu yapmak zorunda olduğumuzun anlamı yok. Başka hiçbir dil gerektirmez.
Andrew Koster

Yanıtlar:


243

Bunun nedeni, işlev türü parametreleri için varsayılan davranıştaki bir değişikliktir. Swift 3'ten önce (özellikle Xcode 8 beta 6 ile birlikte gelen yapı), varsayılan olarak kaçmayı tercih ederler @noescape- saklanmaları veya yakalanmalarını önlemek için onları işaretlemeniz gerekir , bu da süreyi geçmeyeceklerini garanti eder işlev çağrısı.

Ancak, şimdi @noescapeişlev türü parametreler için varsayılan değerdir. Bu tür işlevleri saklamak veya yakalamak istiyorsanız, şimdi bunları işaretlemeniz gerekir @escaping:

protocol DataServiceType {
  func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
  func cachedData(location: String) -> Data?
}

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
  // ...
}

Bu değişiklik hakkında daha fazla bilgi için Swift Evrim teklifine bakın .


2
Ancak, kaçışa izin vermemek için bir kapağı nasıl kullanıyorsunuz?
Eneko Alonso

6
@EnekoAlonso Sorduğunuz şeyden tam olarak emin değilsiniz - ya doğrudan işlevin kendisinden kaçan bir işlev parametresi çağırabilir ya da kaçamayan bir kapanışta yakalandığında bunu çağırabilirsiniz. Bu durumda, eşzamansız kodla uğraştığımız için, asyncişlev parametresinin (ve dolayısıyla completionişlevin) fetchDataçıkışlardan önce çağrıldığının garantisi yoktur ve bu nedenle olması gerekir @escaping.
Hamish

@Escaping'i protokoller için yöntem imzası olarak belirtmemiz gerektiği için çirkin hissediyorum ... ne yapmalıyız? Teklif söylemiyor! : S
Sajjon

1
@Sajjon Şu anda, @escapingbir protokol gereksinimindeki bir @escapingparametreyi bu gereksinimin uygulanmasındaki bir parametreyle eşleştirmeniz gerekir (ve kaçan parametreler için tersi). Swift 2 için de aynıydı @noescape.
Hamish


30

@Noescape varsayılan olduğundan, hatayı düzeltmek için 2 seçenek vardır:

1) @Hamish'in cevabında işaret ettiği gibi, sonuca önem veriyorsanız ve gerçekten kaçmasını istiyorsanız tamamlamayı @ çıkış olarak işaretleyin (muhtemelen @ Lukasz'ın Birim Testleri ile ilgili sorusunda örnek ve zaman uyumsuzluk olasılığı budur tamamlama)

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)

VEYA

2) Sonuçla ilgilenmediğiniz durumlarda tamamlamayı isteğe bağlı hale getirerek varsayılan @ noescape davranışını koruyun. Örneğin, kullanıcı zaten "uzaklaştı" ve arayan görünüm denetleyicisinin sadece dikkatsiz bir ağ çağrısı olduğu için bellekte kalması gerekmediğinde. Buraya cevap aramaya geldiğimde ve örnek kod benim için çok alakalı değildi, bu yüzden @nocape'i işaretlemek en iyi seçenek değildi, olay ilk bakışta tek olarak göründü.

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.