Swift 当完成处理程序明确使用@escaping 时,推断完成处理程序闭包是默认的@nonescaping 而不是@escaping

Swift inferring a completion handler closure to be the default @nonescaping instead of @escaping when completion handler explicitly uses @escaping

Swift4.2,Xcode10.1

在我正在处理的订单处理应用程序中,用户可能会搜索已处理或提交的订单。发生这种情况时,它将检查是否有订单缓存,如果没有,它将使用异步 API 请求重新填充该缓存,然后再次检查缓存。

重新填充缓存的函数是一个私有静态函数,它接受转义完成处理程序。每当我过去使用那个完成处理程序时,我所要做的就是在函数调用的末尾添加一个闭包。这是在我被指示尽可能缓存所有数据之前,并且只使用 API 来重新填充该缓存之前。从那时起,这个函数就变成了私有的,因为除了在 class.

之外,再也不需要从任何地方直接调用 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 的方式存在某种问题。我超出了我的深度。我可以尝试将静态 class 转换为单例,但由于一个错误,我对重构一堆工作代码犹豫不决,直到我完全确定更改将解决问题,而我就是尝试去做是不可能的,除非我改变我的方法。

代码如下:

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?

这个问题与闭包、static、private无关。它与类型参数有关。您不能调用此方法:

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>.