2

我刚刚下载了 Xcode 7 Beta 2,并试图利用我对 Swift 的了解来制作一个从用户的相机胶卷中删除照片的应用程序。我知道如何使用 Swift 1.2 通常做到这一点,但我似乎无法在 Swift 2.0 中得到它。我尝试搜索文档以了解如何在 Swift 2.0 中使用“performChange”功能,但它不起作用。这是我的斯威夫特:

PHPhotoLibrary.sharedPhotoLibrary().performChanges({
    PHAssetChangeRequest.deleteAssets(arrayToDelete)
}, completionHandler: { (success, error) -> Void in
    NSLog("Finished deleting asset. %@", (success ? "Success" : error))
})

这是我的错误:

无法performChanges使用类型的参数列表调用(() -> _, completionHandler: (_, _) -> Void)

任何帮助表示赞赏!

4

1 回答 1

6

Swift 编译器通常在报告复杂表达式中类型检查失败的正确根本原因方面存在问题。我不会简单地向您展示这段代码的正确形式,而是介绍我如何找到那里的方式,以便您可以重用该过程以进行将来的调试。(如果必须,请跳过 TLDR。)


首先,你有一个错误的形式:

无法使用“参数”类型的参数列表调用“函数

这意味着有关您的函数调用的某些内容无法进行类型检查。因为您正在调用参数包含闭包的函数,所以您需要查看闭包的行为以缩小类型检查问题的范围。

让我们首先让闭包显式地返回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 必须考虑每个单独的表达式可能是一个隐式的 return 语句

如果其中一个语句有类型检查错误,则该语句的返回类型变为<<error type>>,并且因为它是单语句闭包,闭包的返回类型变为<<error type>>,因此以闭包为参数的函数调用失败,因为函数期待一个返回的闭包Void,而不是一个返回的闭包<<error type>>

事实上,这就是正在发生的事情——一旦我们进行了上述更改,我们就会在NSLog语句上得到一个不同的错误("Success"突出显示):

'_' 不能转换为 'StringLiteralConvertible'

这仍然有点不清楚,但我们更接近问题的根源。如果你用(success ? "Success" : error)静态的东西(比如 just )替换日志语句的一部分"Success",它就会编译。所以,让我们分离和剖析那个三元运算,看看它里面出了什么问题。

let successString = "Success"
let report = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)

这给我们带来了一个关于?三元运算符的新错误:

“NSString”不是“NSError”的子类型

嗯?与 to 的自动转换有关Swift.StringNSString也许?让我们明确地进行转换以确保:

let successString = "Success" as NSString
let report = (success ? successString : error)

在没有更多上下文的情况下,表达式的类型是模棱两可的

现在我们到达了问题的症结所在。实际上,report应该是什么类型?如果success为真,NSString则为 ,如果为假,则为NSError?(的类型error,从 的声明中推断performChanges(_:completionHandler:))。这种类型的手波会在 C 中流行,但 Swift 对这些事情要严格得多。(曾经非常聪明的人说过,“不完整的类型规范会导致内存布局不清晰,内存布局不明确导致行为未定义,行为未定义导致痛苦。”或类似的话。

NSString和的唯一超类型NSError?Any,而 Swift 不愿意推断该类型。(因为如果您推断一切都可以是任何东西,那么您的类型信息就毫无价值。)如果您尝试手动使用该类型,则在尝试将其传递给时会出错NSLog

let report: Any = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)

无法使用类型为“(String,Any)”的参数列表调用“NSLog”

预期类型为“(字符串,[CVarArgType])”的参数列表

这些错误让我们摆脱了 C vararg 函数的兔子洞,所以让我们退后一步——这个参数NSLog真正需要什么类型?NSLog是一个 ObjC 函数,格式字符串替换(%@业务)基于NSString stringWithFormat. 根据文档,当您使用%@令牌时,NSString会在相应参数中查找对象并调用其description方法。因为该实现是 ObjC 和 Cocoa 框架的一部分(可以追溯到恐龙被消灭之前),而不是 Swift 的东西,所以像这样的纯 Swift 类型Any在这里不起作用是有道理的。

NSObject将是一个很好的 Cocoa 类型传递给NSLog函数。但是,将其声明reportNSObject. 该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为真,error则为 nil,但如果success为假,则实际上会出现错误

(这甚至可能对您有用,而无需在以前的 SDK 版本上解包,因为performChanges(_:completionHandler:)在 Apple 审核其所有 API 的可空性之前,闭包类型会使用隐式解包的可选项。)

于 2015-07-08T20:40:07.057 回答