Закрытие использования параметра, не поддерживающего экранирование, может позволить ему избежать
У меня есть протокол:
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), но больше не работает с бета-версией 6. Можете ли вы указать мне на причину?
Ответы
Ответ 1
Это связано с изменением поведения по умолчанию для параметров типа функции. До Swift 3 (в частности, сборки, которая поставляется с бета-версией Xcode 8), по умолчанию они будут экранироваться - вам нужно будет пометить их @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.
Ответ 2
Так как @noescape по умолчанию, есть 2 варианта исправления ошибки:
1), как отметил в своем ответе @Hamish, просто отметьте завершение как @escaping, если вы позаботились о результате и действительно хотите, чтобы он ушел (что, вероятно, имеет место в вопросе @Lukasz с Unit Tests в качестве примера и возможность асинхронного завершения)
func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
ИЛИ
2) сохраняйте поведение по умолчанию @noescape, сделав завершение опцией, полностью отбрасывая результаты в случаях, когда вам не нужен результат. Например, когда пользователь уже "ушел", а диспетчер вызывающего вида не должен висеть в памяти только потому, что был какой-то неосторожный сетевой вызов. Так же, как это было в моем случае, когда я пришел сюда, ища ответ, и пример кода был для меня не очень уместным, поэтому маркировка @noescape была не лучшим вариантом, но это звучало как единственное с первого взгляда.
func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
...
completion?(self.result)
}