Закрытие использование параметра 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)
}