Saya memiliki protokol:
enum DataFetchResult {
case success(data: Data)
case failure
}
protocol DataServiceType {
func fetchData(location: String, completion: (DataFetchResult) -> (Void))
func cachedData(location: String) -> Data?
}
Dengan contoh implementasi:
/// 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)
}
}
}
Kode di atas dikompilasi dan berfungsi di Swift3 (Xcode8-beta5) tetapi tidak berfungsi dengan beta 6 lagi. Bisakah Anda mengarahkan saya ke penyebab yang mendasarinya?
swift
swift3
closures
xcode8-beta6
Lukasz
sumber
sumber
Jawaban:
Ini karena perubahan perilaku default untuk parameter tipe fungsi. Sebelum Swift 3 (khususnya build yang dikirimkan dengan Xcode 8 beta 6), mereka secara default akan melarikan diri - Anda harus menandai mereka
@noescape
untuk mencegah mereka disimpan atau ditangkap, yang menjamin mereka tidak akan hidup lebih lama dari durasinya dari pemanggilan fungsi.Namun, sekarang
@noescape
adalah default untuk parameter tipe fungsi. Jika Anda ingin menyimpan atau menangkap fungsi seperti itu, Anda sekarang perlu menandainya@escaping
:protocol DataServiceType { func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) func cachedData(location: String) -> Data? }
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) { // ... }
Lihat proposal Evolusi Swift untuk info lebih lanjut tentang perubahan ini.
sumber
async
parameter fungsi (dan oleh karena itucompletion
fungsi) akan dipanggil sebelumfetchData
keluar - dan oleh karena itu harus dipanggil@escaping
.@escaping
parameter dalam persyaratan protokol dengan@escaping
parameter dalam penerapan persyaratan itu (dan sebaliknya untuk parameter non-pelolosan). Itu sama di Swift 2 untuk@noescape
.Karena @noescape adalah defaultnya, ada 2 opsi untuk memperbaiki kesalahan:
1) seperti yang ditunjukkan @Hamish dalam jawabannya, cukup tandai penyelesaian sebagai @escaping jika Anda benar-benar peduli dengan hasilnya dan benar-benar ingin hasilnya lolos (mungkin itu yang terjadi pada pertanyaan @ Lukasz dengan Unit Tests sebagai contoh dan kemungkinan asinkron penyelesaian)
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
ATAU
2) pertahankan perilaku @noescape default dengan membuat penyelesaian opsional dengan membuang hasil sama sekali jika Anda tidak peduli dengan hasilnya. Misalnya ketika pengguna sudah "pergi" dan pengontrol tampilan panggilan tidak harus menggantung di memori hanya karena ada beberapa panggilan jaringan yang ceroboh. Sama seperti kasus saya ketika saya datang ke sini untuk mencari jawaban dan kode sampelnya tidak terlalu relevan bagi saya, jadi menandai @noescape bukanlah pilihan terbaik, meskipun itu terdengar sebagai satu-satunya dari pandangan pertama.
func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) { ... completion?(self.result) }
sumber