Swift 编译器通常在报告复杂表达式中类型检查失败的正确根本原因方面存在问题。我不会简单地向您展示这段代码的正确形式,而是介绍我如何找到那里的方式,以便您可以重用该过程以进行将来的调试。 (如果必须,请跳过 TLDR。)
首先,您的表单有错误:
无法使用 'params'
类型的参数列表调用 'function'
这意味着您的函数调用的某些内容未能通过类型检查。因为您正在调用一个参数包含闭包的函数,所以您需要查看闭包的行为以缩小类型检查问题的范围。
让我们首先让闭包显式返回Void:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(arrayToDelete)
return
}, completionHandler: { (success, error) -> Void in
NSLog("Finished deleting asset. %@", (success ? "Success" : error))
return
})
这里发生了什么?您已经将完成处理程序的类型声明为返回Void,那么为什么还要额外的return 语句呢? Swift 类型检查既可以自下而上也可以自上而下工作,如果任何一种方式都存在错误,它就不能对另一个做出假设。在这里,你有两个单表达式闭包,所以 Swift 必须考虑每个单独的表达式可能是 an implicit return statement。
如果其中一个语句有类型检查错误,则该语句的返回类型变为<<error type>>,并且由于它是单语句闭包,闭包的返回类型变为<<error type>>,因此该函数调用的闭包是一个参数失败,因为该函数需要一个返回 Void 的闭包,而不是返回 <<error type>> 的闭包。
确实是这样——一旦我们进行了上述更改,我们就会在NSLog 语句上得到一个不同的错误("Success" 突出显示):
'_' 不能转换为 'StringLiteralConvertible'
这仍然有点不清楚,但我们更接近问题的根源。如果您将日志语句的(success ? "Success" : error) 部分替换为静态内容(例如,只是"Success"),它将编译。所以,让我们分离和剖析这个三元运算,看看它里面出了什么问题。
let successString = "Success"
let report = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)
这给我们带来了一个新的错误,在三元运算符的? 上:
'NSString' 不是'NSError' 的子类型
嗯?可能与Swift.String 到NSString 的自动转换有关吗?让我们明确转换以确保:
let successString = "Success" as NSString
let report = (success ? successString : error)
没有更多上下文,表达式的类型是模棱两可的
现在我们到达了问题的症结所在。实际上,report 的类型应该是什么?如果success 为真,则为NSString,如果为假,则为NSError?(error 的类型,由performChanges(_:completionHandler:) 的声明推断)。这种类型的手波会在 C 中流行,但 Swift 对这些事情要严格得多。 (非常聪明的人曾经说过,“不完整的类型规范导致不明确的内存布局,不明确的内存布局导致未定义的行为,未定义的行为导致痛苦。”Or something like that.)
NSString 和 NSError? 的唯一超类型是 Any,Swift 不愿意推断该类型。 (因为如果您推断一切都可以是任何东西,那么您的类型信息就毫无价值。)如果您尝试手动使用该类型,则在尝试将其传递给 NSLog 时会出错:
let report: Any = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)
无法使用类型为“(String, Any)”的参数列表调用“NSLog”
需要一个类型为'(String, [CVarArgType])'的参数列表
这些错误让我们陷入了 C 可变参数函数的困境,所以让我们退后一步——NSLog 真正想要这个参数的类型是什么? NSLog 是一个 ObjC 函数,具有基于 NSString stringWithFormat 的格式字符串替换(%@ 业务)。 Per the docs,当您使用%@ 令牌时,NSString 在相应参数中查找对象并调用其description 方法。因为该实现是 ObjC 和 Cocoa 框架的一部分(可以追溯到 before the dinosaurs were wiped out),而不是 Swift 的东西,所以像 Any 这样的纯 Swift 类型在这里不起作用。
NSObject 将是一个很好的 Cocoa 类型,可以传递给 NSLog 函数。但是,声明 report 的类型也不会运行 - 您不能将三元运算符的两个分支隐式转换为 NSObject。 error 参数是可选的——它的推断类型是NSError?,记得吗?那是 Swift 类型,而不是 Cocoa 类型。
所以这是最后一个问题——你有一个三元运算符,它试图让一个分支成为一个完全合理的对象,而另一个分支是一个仍然包装的可选对象。事实上,强制解包可选项会清除所有编译器错误:
let report = (success ? successString : error!) // report now type-infers NSObject
NSLog("Finished deleting asset. %@", report)
现在我们已经把所有东西都修好了,我们可以重新装上轮子,然后把所有东西都倒回去……
TLDR:解开您的选项。
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(arrayToDelete)
}, completionHandler: { success, error in
NSLog("Finished deleting asset. %@", (success ? "Success" : error!))
})
我们知道,由于 API 契约,在这里强制解包是安全的:如果 success 为 true,error 将为 nil,但如果 success 为 false,则实际上将有 an error。
(这甚至可能对您有用,而无需在以前的 SDK 版本上解包,因为在 Apple 审核其所有 API 的可空性之前,performChanges(_:completionHandler:) 中的闭包类型会使用隐式解包选项。)