Reputation: 21
I just downloaded Xcode 7 Beta 2 and am trying to use my knowledge of Swift to make an app that deletes a photo from the user's camera roll. I know how to do this normally with Swift 1.2, but I can't seem to get it in Swift 2.0. I tried searching through the documentation to learn how to use the 'performChange' function in Swift 2.0, but it won't work. Here's my Swift :
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(arrayToDelete)
}, completionHandler: { (success, error) -> Void in
NSLog("Finished deleting asset. %@", (success ? "Success" : error))
})
Here's my error:
Cannot invoke
performChanges
with an argument list of type(() -> _, completionHandler: (_, _) -> Void)
Any help is appreciated!!
Upvotes: 2
Views: 1222
Reputation: 126167
The Swift compiler generally has issues with reporting the correct root cause of a type-checking failure in a complex expression. Rather than simply show you the correct form of this code, I'll walk through how I found the way there, so you can reuse that process for future debugging. (Skip down for the TLDR if you must.)
First, you've got an error of the form:
Cannot invoke 'function' with an argument list of type 'params'
That means that something about your function call has failed to type-check. Because you're calling a function where the parameters include closures, you'll need to look at the behavior of the closures to narrow down the type-checking issues.
Let's start by making the closures explicitly return Void
:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(arrayToDelete)
return
}, completionHandler: { (success, error) -> Void in
NSLog("Finished deleting asset. %@", (success ? "Success" : error))
return
})
What's going on here? You've already declared the type of the completion handler as returning Void
, so why the extra return
statement? Swift type checking works both bottom-up and top-down, and if there's an error along either way, it can't make assumptions about the other. Here, you have two single-expression closure, so Swift has to consider that each one's single expression could be an implicit return statement.
If one of those statements has a type-checking error, that statement's return type becomes <<error type>>
, and because it's a single-statement closure, the closure's return type becomes <<error type>>
, and therefore the function call to which the closure is a parameter fails, because the function is expecting a closure that returns Void
, not a closure that returns <<error type>>
.
Indeed that's what's happening — once we make the above change, we get a different error, on the NSLog
statement (with "Success"
highlighted):
'_' is not convertible to 'StringLiteralConvertible'
That's a bit unclear still, but we're closer to the root of the problem. If you replace the (success ? "Success" : error)
part of the log statement with something static (say, just "Success"
), it compiles. So, let's separate and dissect that ternary operation to see what's going wrong inside it.
let successString = "Success"
let report = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)
This gets us a new error, on the ?
of the ternary operator:
'NSString' is not a subtype of 'NSError'
Huh? Something to do with automatic conversion of Swift.String
to NSString
, maybe? Let's make that conversion explicit to be sure:
let successString = "Success" as NSString
let report = (success ? successString : error)
type of expression is ambiguous without more context
Now we reach the crux of the matter. What, indeed, is the type of report
supposed to be? If success
is true, it's NSString
, but if false, it's NSError?
(the type of error
, inferred from the declaration of performChanges(_:completionHandler:)
). This sort of type hand-waviness will fly in C, but Swift is much more strict about such things. (Someone very wise once said, "Incomplete type specification leads to unclear memory layout, unclear memory layout leads to undefined behavior, undefined behavior leads to suffering." Or something like that.)
The only supertype of both NSString
and NSError?
is Any
, and Swift is reluctant to infer that type. (Because if you infer that everything can be anything, your type information is worthless.) And if you try to use that type manually, you get errors when you try to pass it to NSLog
:
let report: Any = (success ? successString : error)
NSLog("Finished deleting asset. %@", report)
cannot invoke 'NSLog' with an argument list of type '(String, Any)'
expected an argument list of type '(String, [CVarArgType])'
Those errors take us off down the rabbit hole of C vararg functions, so let's step back a bit — what type does NSLog
really want for this argument? NSLog
is an ObjC function, with format string substitution (the %@
business) built on NSString stringWithFormat
. Per the docs, when you use a %@
token, NSString
looks for an object in the corresponding parameter and calls its description
method. Because that implementation is ObjC and part of the Cocoa frameworks (dating back to before the dinosaurs were wiped out), not a Swift thing, it stands to reason that a pure-Swift type like Any
won't work here.
NSObject
would be a good Cocoa type to pass to the NSLog
function. However, declaring that as the type of report
won't fly, either — you can't implicitly convert both branches of the ternary operator to NSObject
. The error
parameter is an optional — its inferred type is NSError?
, remember? And that's a Swift type, not a Cocoa type.
So that's the final problem — you have a ternary operator that's trying to have one branch be a perfectly sensible object, and the other branch a still-wrapped optional. Indeed, force-unwrapping the optional clears all the compiler errors:
let report = (success ? successString : error!) // report now type-infers NSObject
NSLog("Finished deleting asset. %@", report)
Now that we've fixed everything, we can put the wheels back on and collapse everything back down...
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(arrayToDelete)
}, completionHandler: { success, error in
NSLog("Finished deleting asset. %@", (success ? "Success" : error!))
})
We know it's safe to force-unwrap here because of the API contract: if success
is true, error
will be nil, but if success
is false, there will actually be an error.
(This may have even worked for you without unwrapping on previous SDK versions because the closure type in the performChanges(_:completionHandler:)
would have used an implicitly-unwrapped optional before Apple audited all their APIs for nullability.)
Upvotes: 6