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
performChangeswith an argument list of type(() -> _, completionHandler: (_, _) -> Void)
Any help is appreciated!!
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:
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:What's going on here? You've already declared the type of the completion handler as returning
Void, so why the extrareturnstatement? 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 returnsVoid, 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
NSLogstatement (with"Success"highlighted):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.This gets us a new error, on the
?of the ternary operator:Huh? Something to do with automatic conversion of
Swift.StringtoNSString, maybe? Let's make that conversion explicit to be sure:Now we reach the crux of the matter. What, indeed, is the type of
reportsupposed to be? Ifsuccessis true, it'sNSString, but if false, it'sNSError?(the type oferror, inferred from the declaration ofperformChanges(_: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
NSStringandNSError?isAny, 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 toNSLog:Those errors take us off down the rabbit hole of C vararg functions, so let's step back a bit — what type does
NSLogreally want for this argument?NSLogis an ObjC function, with format string substitution (the%@business) built onNSString stringWithFormat. Per the docs, when you use a%@token,NSStringlooks for an object in the corresponding parameter and calls itsdescriptionmethod. 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 likeAnywon't work here.NSObjectwould be a good Cocoa type to pass to theNSLogfunction. However, declaring that as the type ofreportwon't fly, either — you can't implicitly convert both branches of the ternary operator toNSObject. Theerrorparameter is an optional — its inferred type isNSError?, 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:
Now that we've fixed everything, we can put the wheels back on and collapse everything back down...
TLDR: Unwrap your optionals.
We know it's safe to force-unwrap here because of the API contract: if
successis true,errorwill be nil, but ifsuccessis 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.)