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

Posted

技术标签:

【中文标题】当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping【英文标题】:Swift inferring a completion handler closure to be the default @nonescaping instead of @escaping when completion handler explicitly uses @escaping 【发布时间】:2019-03-05 21:18:08 【问题描述】:

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)

为什么在它总是推断它是@escaping 之前,它总是推断这个闭包是默认的@nonescaping?

【问题讨论】:

我更怀疑签名中的CodableClass.Type(而不是预期的Codable.Type)。那是什么? getAndCacheAPIData 的签名是什么? codableType的定义是什么? 私有静态函数 getAndCacheAPIData (type:CodableClass.Type, completionHandler: @escaping (String?)->Void) CodableClass 只是一种动态类型,因此我可以将 API 数据解码为多个 Codable 结构,而无需为每个结构提供不同的功能。它适用于我放入的任何 Codable 数据类型。 我对 Swift 很陌生,所以我可能会以一种不太理想的方式来做这件事 codableType 在代码的前面基于枚举值分配,它总是会评估为符合 Codable 的某种类型 【参考方案1】:

问题与闭包、静态或私有无关。它与类型参数有关。你不能调用这个方法:

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 &amp; 在这里毫无意义。你的意思是&lt;CodableClass: Codable&gt;

【讨论】:

谢谢,这解决了问题。我正在阅读 CodableClass 和 Codable 作为同一件事,我从未注意到它们不同。呵呵。

以上是关于当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping的主要内容,如果未能解决你的问题,请参考以下文章

如何使用完成处理程序和参数调用函数

Swift @escaping 和完成处理程序

Swift 4 完成处理程序:数组保持为空

存储完成处理程序并稍后调用成功/错误

完成处理程序混淆

Swift 中的完成处理程序 Firebase 观察者