当完成处理程序显式使用 @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 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 &
在这里毫无意义。你的意思是<CodableClass: Codable>
。
【讨论】:
谢谢,这解决了问题。我正在阅读 CodableClass 和 Codable 作为同一件事,我从未注意到它们不同。呵呵。以上是关于当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping的主要内容,如果未能解决你的问题,请参考以下文章