Mam protokół:
enum DataFetchResult {
case success(data: Data)
case failure
}
protocol DataServiceType {
func fetchData(location: String, completion: (DataFetchResult) -> (Void))
func cachedData(location: String) -> Data?
}
Z przykładową realizacją:
/// 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)
}
}
}
Powyższy kod został skompilowany i działał w Swift3 (Xcode8-beta5), ale nie działa już z wersją beta 6. Czy możesz wskazać mi przyczynę?
swift
swift3
closures
xcode8-beta6
Łukasz
źródło
źródło
Odpowiedzi:
Wynika to ze zmiany domyślnego zachowania parametrów typu funkcji. Przed Swift 3 (w szczególności kompilacją dostarczaną z Xcode 8 beta 6), domyślnie uciekały - musiałbyś je oznaczyć
@noescape
, aby zapobiec ich przechowywaniu lub przechwyceniu, co gwarantuje, że nie przetrwają wywołania funkcji.Jednak teraz
@noescape
jest wartością domyślną dla parametrów o typie funkcji. Jeśli chcesz przechowywać lub przechwytywać takie funkcje, musisz je teraz zaznaczyć@escaping
:Zobacz propozycję Swift Evolution, aby uzyskać więcej informacji na temat tej zmiany.
źródło
async
parametr funkcji (a tym samymcompletion
funkcja) zostanie wywołana przed zakończeniemfetchData
- i dlatego musi być@escaping
.@escaping
parametr w wymaganiu protokołu z@escaping
parametrem w implementacji tego wymagania (i odwrotnie w przypadku parametrów bez zmiany znaczenia). Tak samo było w Swift 2 dla@noescape
.Ponieważ @noescape jest ustawieniem domyślnym, istnieją 2 opcje naprawienia błędu:
1) jak @Hamish wskazał w swojej odpowiedzi, po prostu zaznacz ukończenie jako @escaping, jeśli zależy ci na wyniku i naprawdę chcesz, aby uciekł (tak prawdopodobnie jest w pytaniu @ Łukasza z testami jednostkowymi jako przykładem i możliwością asynchronizacji ukończenie)
LUB
2) zachowaj domyślne zachowanie @noescape, ustawiając uzupełnianie jako opcjonalne, całkowicie odrzucając wyniki w przypadkach, gdy nie dbasz o wynik. Na przykład, gdy użytkownik już „odszedł” i wywołujący kontroler widoku nie musi zawieszać się w pamięci tylko z powodu nieostrożnego połączenia sieciowego. Tak jak to było w moim przypadku, kiedy przyszedłem tutaj, szukając odpowiedzi, a przykładowy kod nie był dla mnie zbyt istotny, więc oznaczenie @noescape nie było najlepszą opcją, chociaż brzmiało to jako jedyne na pierwszy rzut oka.
źródło