Закрытие использование параметра non-escaping может позволить ему избежать
у меня есть протокол:
enum DataFetchResult {
case success(data: Data)
case failure
}
protocol DataServiceType {
func fetchData(location: String, completion: (DataFetchResult) -> (Void))
func cachedData(location: String) -> Data?
}
С примером реализации:
/// 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)
}
}
}
приведенный выше код скомпилирован и работал в Swift3 (Xcode8-beta5), но больше не работает с beta 6. Вы можете указать мне на основную причину?
2 ответов
Это связано с изменением поведения по умолчанию для параметров функции. До Swift 3 (в частности, сборки, которая поставляется с Xcode 8 beta 6), они по умолчанию будут убегать – вам придется пометить их как @noescape
чтобы предотвратить их хранение или захват, поэтому гарантируя, что они не будут вызываться после выхода функции.
@noescape
по умолчанию – теперь вы должны отметить параметры функции @escaping
чтобы сообщить компилятору, что они может храниться или захватываться.
protocol DataServiceType {
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
func cachedData(location: String) -> Data?
}
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
// ...
}
посмотреть предложение Swift Evolution для получения дополнительной информации об этом изменении.
Так как @noescape по умолчанию, есть 2 варианта, чтобы исправить ошибку:
1) как указал @Hamish в своем ответе, просто отметьте завершение как @escaping, если вы действительно заботитесь о результате и действительно хотите, чтобы он убежал (это, вероятно, имеет место в вопросе @Lukasz с модульными тестами в качестве примера и возможностью асинхронного завершения)
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
или
2) сохраните поведение по умолчанию @noescape, сделав завершение необязательным отбрасыванием результатов в общем, в тех случаях, когда вас не волнует результат. Например, когда пользователь уже "ушел", и вызывающий контроллер представления не должен зависать в памяти только потому, что был какой-то неосторожный сетевой вызов. Так же, как это было в моем случае, когда я пришел сюда в поисках ответа и пример кода не очень актуальны для меня, поэтому маркировка @noescape не лучший вариант, хотя это звучало как единственная с первого взгляда.
func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
...
completion?(self.result)
}