Подтвердить что ты не робот

Swift 3.0 Ошибка: закрытие закрытий может только фиксировать inout параметры явно по значению

Я пытаюсь обновить свой проект до Swift 3.0, но у меня есть некоторые трудности. Я получаю следующую ошибку: "Запуск закрытий может только фиксировать inout параметры явно по значению".

Проблема находится внутри этой функции:

fileprivate func collectAllAvailable(_ storage: inout [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) {
    if let client = self.client {
        let _ : T? = client.collectionItems(nextUrl) {

            (resultCollection, error) -> Void in

            guard error == nil else {
                completion(nil, error)
                return
            }

            guard let resultCollection = resultCollection, let results = resultCollection.results else {
                completion(nil, NSError.unhandledError(ResultCollection.self))
                return
            }

            storage += results // Error: Escaping closures can only capture inout parameters explicitly by value

            if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {

                self.collectAllAvailable(&storage, nextUrl: nextUrlItr, completion: completion) 
                // Error: Escaping closures can only capture inout parameters explicitly by value

            } else {
                completion(storage, nil) 
                // Error: Escaping closures can only capture inout parameters explicitly by value
            }
        }
    } else {
        completion(nil, NSError.unhandledError(ResultCollection.self))
    }
}

Может кто-нибудь помочь мне исправить это?

4b9b3361

Ответ 1

Использование параметра inout исключительно для асинхронной задачи - это злоупотребление inout - как при вызове функции значение вызывающего абонента, которое передается в параметр inout, не будет изменено.

Это связано с тем, что inout не является передачей по ссылке, это просто изменяемая теневая копия параметра, записанного обратно вызывающему, когда функция завершается - и поскольку асинхронная функция выходит немедленно, никакие изменения не будут напишите.

Это можно увидеть в следующем примере Swift 2, где параметр inout разрешен для захвата с помощью закрывающего закрытия:

func foo(inout val: String, completion: (String) -> Void) {
    dispatch_async(dispatch_get_main_queue()) {
        val += "foo"
        completion(val)
    }
}

var str = "bar"
foo(&str) {
    print($0) // barfoo
    print(str) // bar
}
print(str) // bar

Поскольку закрытие, которое передается в dispatch_async, ускоряет время жизни функции foo, любые изменения, которые она делает на val, не записываются обратно вызывающему абоненту str - это изменение наблюдаемо только из передается в функцию завершения.

В параметрах Swift 3 параметры inout больше не могут быть захвачены закрытием @escaping, что устраняет путаницу в ожидании передачи по ссылке. Вместо этого вы должны зафиксировать параметр, скопировав его, добавив его в список :

func foo(val: inout String, completion: @escaping (String) -> Void) {
    DispatchQueue.main.async {[val] in // copies val
        var val = val // mutable copy of val
        val += "foo"
        completion(val)
    }
    // mutate val here, otherwise there no point in it being inout
}

( Изменить:. После отправки этого ответа параметры inout теперь могут быть скомпилированы как перекрестная ссылка, что можно увидеть, посмотрев на SIL или ИК излучаемый. неспособность рассматривать их как таковые из-за того, что нет никакой гарантии, что значение вызывающего абонента останется в силе после вызова функции.)


Однако в вашем случае просто нет необходимости в inout. Вам просто нужно добавить результирующий массив из вашего запроса к текущему массиву результатов, которые вы передаете каждому запросу.

Например:

fileprivate func collectAllAvailable(_ storage: [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) {
    if let client = self.client {
        let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in

            guard error == nil else {
                completion(nil, error)
                return
            }

            guard let resultCollection = resultCollection, let results = resultCollection.results else {
                completion(nil, NSError.unhandledError(ResultCollection.self))
                return
            }

            let storage = storage + results // copy storage, with results appended onto it.

            if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {
                self.collectAllAvailable(storage, nextUrl: nextUrlItr, completion: completion) 
            } else {
                completion(storage, nil) 
            }
        }
    } else {
        completion(nil, NSError.unhandledError(ResultCollection.self))
    }
}

Ответ 2

Если вы хотите изменить переменную, переданную по ссылке, в закрытии экранирования, вы можете использовать KeyPath. Вот пример:

class MyClass {
    var num = 1
    func asyncIncrement(_ keyPath: WritableKeyPath<MyClass, Int>) {
        DispatchQueue.main.async {
            // Use weak to avoid retain cycle
            [weak self] in
            self?[keyPath: keyPath] += 1
        }
    }
}

Вы можете увидеть полный пример здесь.