Swift выводит закрытие обработчика завершения как значение по умолчанию @nonescaping вместо @escaping, когда обработчик завершения явно использует @escaping - PullRequest
0 голосов
/ 06 марта 2019

Swift 4.2, Xcode 10.1

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

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

Теперь, когда я помещаю замыкание непосредственно после вызова функции, оно выдает мне ошибку, которая в основном говорит, что я передаю замыкание @nonescaping вместо замыкания @escaping:

"Cannot invoke 'getAndCacheAPIData' with an argument list of type '(type: Codable.Type, (String?) -> Void)', Expected an argument list of type '(type: CodableClass.Type, @escaping (String?) -> Void)'"

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

Вот код:

public static func fillSearchResultArray<ManagedClass: NSManagedObject>(query:String, parameters:[String], with type: ManagedClass.Type, completionHandler: @escaping (String?)->Void)
{
    let codableType:Codable.Type
    switch type
    {
        case is ClientTable.Type:
            codableType = ClientData.self
        case is OrderTable.Type:
            codableType = OrderData.self
        case is ProductTable.Type:
            codableType = ProductData.self
        default:
            completionHandler("Unrecognized type.")
            return
    }
    let fetchedData:[ManagedClass]
    do
    {
        fetchedData = try PersistenceManager.shared.fetch(ManagedClass.self)
    }
    catch
    {
        completionHandler(error.localizedDescription)
        return
    }

    if fetchedData.isEmpty
    {
        AppNetwork.getAndCacheAPIData(type: codableType)//error here
        {(firstErrorString) in
            //move search array data to the cache
            if firstErrorString.exists
            {
                completionHandler(error)
            }
            else
            {
                AppNetwork.fillSearchResultArray(query: query, parameters: parameters, type: type)
                { errorString in
                    completionHandler(errorString)
                }
            }
        }

        return
    }
    else
    { ...

Подпись вызываемой функции:

private static func getAndCacheAPIData <CodableClass: Any & Codable>(type:CodableClass.Type, completionHandler: @escaping (String?)->Void)

Почему swift выводит, что это закрытие является @nonescaping по умолчанию, когда до него всегда делалось @escaping?

1 Ответ

1 голос
/ 06 марта 2019

Проблема не имеет ничего общего с замыканием, статическим или частным.Это связано с параметром типа.Вы не можете вызвать этот метод:

private static func getAndCacheAPIData <CodableClass: Any & Codable>(type:CodableClass.Type, completionHandler: @escaping (String?)->Void)

с переменной типа Codable.Type.Передаваемое вами значение типа должно быть конкретным типом, известным во время компиляции.Если вы хотите передать переменную, вы не можете использовать универсальный.Это должно быть:

private static func getAndCacheAPIData(type: Codable.Type, completionHandler: @escaping (String?)->Void)

Альтернативно, вы можете назвать это как:

 AppNetwork.getAndCacheAPIData(type: Int.self) {(firstErrorString) in ... }

или какой-либо другой тип, известный во время компиляции.

Вероятно, что вы действительно хотите здесь, это что-то вроде:

let completion: (String?) -> Void = {(firstErrorString) in ... }

switch ... {
    case ...:
        AppNetwork.getAndCacheAPIData(type: Int.self, completion: completion)
    case ...:
        AppNetwork.getAndCacheAPIData(type: String.self, completion: completion)
    ...

Основная проблема заключается в том, что протоколы не соответствуют самим себе, поэтому переменная типа Codable.Type не удовлетворяет требованию : Codable.Это сводится к той же причине, по которой вы не можете просто позвонить:

AppNetwork.getAndCacheAPIData(type: Codable.self) {...}

В качестве альтернативы, вы можете изменить его следующим образом:

private static func handleAPI<CodableClass: Codable>(type: CodableClass.Type) {
    getAndCacheAPIData(type: type.self) { _ in ... the completion handler ..}
}


switch ... {
    case ...:
        AppNetwork.handleAPI(type: Int.self)
    case ...:
        AppNetwork.handleAPI(type: String.self)
    ...

Примечание: Any & здесь не имеет смысла,Вы просто имели в виду <CodableClass: Codable>.

...