Swift 2.0 new 'perform changes' of PHPPhotoLibrary won't work - swift

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!!

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...
TLDR: Unwrap your optionals.
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.)

Related

In Swift, an Objective-C BOOL inferred as `()` instead of Bool

I've got a method written in Objective-C which returns a BOOL, for example:
(BOOL)methodName:(NSDictionary<NSString *, NSString *> *)params callback:(void(^)(NSString *_Nullable, ErrorInformation *_Nullable))callback error:(NSError *_Nullable *_Nullable)errorPtr;
Usage in Swift
I get the error, Cannot convert value of type '()' to expected condition type 'Bool'. I thinks that ret is of type (), instead of BOOL. Looking at the implementation, this value is mutated inside dispatch_sync.
let ret = try! methodName()
// I've tried a bunch of different syntaxes below:
if (ret) { <--- Xcode warning: Cannot convert value of type '()' to expected condition type 'Bool'
}
It is not nice to see this method has 3 ways of indicating failure, but I didn't design it 😅 and frankly my objective-C is not good:
errorPtr, which is automatically turned into do/try/catch in Swift
ErrorInformation passed in the callback
BOOL return value, which I am struggling with.
The returned BOOL is part of the NSError processing that is converted in Swift into a throws function (if the method returns a value, Swift will convert it from nullable to nonnull).
YES is returned if the method succeeds (there is no error), NO is returned when the method fails.
In Swift:
do {
try methodName()
// method succeeded
} catch {
// method failed
}
The ErrorInformation in the callback is probably related to asynchronous errors, probably similar to a Result<String, Error> in Swift.
References:
Handling Error Objects Returned From Methods (Obj-C)
Improved NSError Bridging (Swift Evolution 0112)

Swift Compiler Error,Expression type 'Error' is ambiguous without more context

typealias SwiftAMapCompletion = (CLLocation?,AMapLocationReGeocode?,Error) -> Void
var locationResult : SwiftAMapCompletion?
I want to give a nil as Error, but "Swift Compiler Error" is
Expression type 'Error' is ambiguous without more context.(SwiftAMapCompletion can't change)
locationResult!(location, reGeocode, nil as! Error)
You cannot force nil to be an Error, not even if you use as!.
Your options are:
Change the declaration to be Error?, because that would mean that you can pass nil.
Pass an Error.
For example:
enum MyError: Error {
case ok
}
locationResult!(location, reGeocode, MyError.ok)
In my opinion, your SwiftAMapCompletion interface does not make any sense, because normally a callback like this would be "here is the result, or here is the error" so all parameters should be declared as optional (with ?). I would get this interface changed if you can.

What does () mean in Swift?

I have the following function in Swift 3
func fetchOrders(_ completionHandler: (_ orders: [Order]) -> Void)
{
ordersStore.fetchOrders { (orders: () throws -> [Order]) -> Void in
do {
let orders = try orders()
completionHandler(orders)
} catch {
completionHandler([])
}
}
}
What does _ completionHandler argument in fetchOrders mean?
What does (orders: () throws -> [Order]) mean?
PS : I am new to iOS and Swift
There's quite a lot in here, so we'll break it down one piece at a time:
func fetchOrders(_ completionHandler: (_ orders: [Order]) -> Void)
This is a function called fetchOrders.
It has one parameter (completionHandler) and returns nothing.
The first _ indicates that there is no "external name" of the first parameter. That is, you do not have to label it (in fact, you cannot). (For subtle reasons that don't really matter here, I believe the author made a mistake using _ there, and I would not have done that.)
The completionHandler is the "internal name," what the parameter is called inside the function.
The type of completionHandler is (_ orders: [Order]) -> Void. We'll break that down now.
This value is a closure that takes an [Order] (array of Order) and returns Void. Informally this means "returns nothing" but literally means it returns the empty tuple ().
The _ orders: syntax is in practice a comment. In principle the _ is an external name (but that's the only legal external name for a closure), and orders is an internal name, but in reality, closures parameters do not have names in any meaningful way, so this is purely informational.
I believe this is a poor use of the closure parameter commenting system. Since orders tells us nothing more than [Order], I would have omitted it, and made the type just ([Order]) -> Void.
Now we'll turn to the next line:
ordersStore.fetchOrders { (orders: () throws -> [Order]) -> Void in
This calls the fetchOrders method on ordersStore. We can tell from this code that fetchOrders takes a closure parameter. This is called "trailing closure" syntax in Swift, and is why I would not have used the _ for our closure. With trailing closure syntax, the external name of the parameter is not needed.
The author has provided type information here that probably wasn't necessary, but we can explore it anyway. This could likely have been written as just { orders in, but then the reader would probably have been surprised by this somewhat unusual code.
We have been passed a closure called orders that takes nothing and returns [Order] or throws an error. Basically this is a way to say that fetchOrders might fail.
The author is working around an awkwardness in Swift's throws system, which does not have a natural way to express an asynchronous action that might fail. This is one way to fix it; you pass a throwing (i.e. a possibly failing) function. I don't favor this approach, I favor using a Result enum for this case because I think it scales better and avoids possible unintended side effects, but that's a debatable point (and the Swift community hasn't really decided how to deal with this common problem).
This all leads us to:
do {
let orders = try orders()
completionHandler(orders)
} catch {
completionHandler([])
}
This is where the orders closure is evaluated. (This is very important; if orders has side effects, this is when they occur, which may be on a different queue than was intended. That's one reason I don't favor this pattern.) If the closure succeeds, we return its result, otherwise we return [] in the catch below.
In this particular case, the throws approach is slightly silly, because it's silently flattened into [] without even a log message. If we don't care about the errors, then failure should have just returned [] to start with and not messed with throws. But it's possible that other callers do check the errors.
In either case, we call the completionHandler closure with our result, chaining this back to our original caller.
This do/catch block could have been more simply written as:
let completedOrders = try? orders() ?? []
completionHandler(completedOrders)
This makes it clearer that we're ignoring errors by turning it into an optional, and avoids code duplication of the call to completionHandler.
(I just add the extra let binding to make the code a little easier to read; it isn't needed.)
The completionHandler argument means that the expected parameter (named completionHandler) must be a function that takes a list of Order objects and does not return any value.
completionHandler is the a variable name. In this specific example, this variable is a callback. You know is a callback function because (orders: [Order]) -> Void is it's data type; in this particular case, said data type is a function that receives an array of Order objects in a variable _orders and doesn't have a return value (the Void part).
TL;DR:
it's the variable name, of type:
function which receives an array of Order as a parameter and acts as a callback.

Completion closure within a closure

Just starting to learn Swift coming from Obj-C - this is something simple I'm not understanding:
class func queryForAllUsersWithCallback(completion: (users :[User]?, error :NSError?) ->()) {
var query = PFQuery(className:User.parseClassName())
query.findObjectsInBackgroundWithBlock ({
(objects:[AnyObject]?, error: NSError?) in
completion(users: objects, error: error);
})
}
Give me a compiler error:
Cannot invoke 'findObjectsInBackgroundWithBlock' with an argument list of type '(([AnyObject]?, NSError?) -> _)'
If I comment out the line:
completion(users: objects, error: error);
the error goes away, so the warning is misleading.
completion takes as its first argument an array of User, whereas objects is an array of AnyObject. There’s no guarantee what is in objects is of the correct type (could be a motley collection of various types for all the compiler knows) so it won’t compile.
If you do a conditional cast it should compile, i.e.:
completion(users: objects as? [User], error: error)
Note, this will check at runtime that every element in objects really is of the correct type. If any of them aren’t, the whole array will be nil when passed to the completion handler. This will compile, since the argument is optional, but might be quite surprising/fail silently or even worse, crash because somewhere inside completion might be the assumption it isn’t nil, so it could get force-unwrapped.
So you might instead want to put some error handling in:
if let users = objects as? [User] {
completion(users: users, error: error)
}
else {
// log or fatalError or something
}
(apologies if the syntax of some of the above isn’t quite right, I haven’t tested the code since your snippet isn’t reproducible/stand-alone)
You just need to cast the objects to User as:
completion(users: objects as? [User], error: error)

'NSSecureCoding!' is not a subtype of 'NSURL' using Swift when trying to replace closure argument type

I'm using the
loadItemForTypeIdentifier:options:completionHandler: method on an NSItemProvider object to extract a url from Safari via a Share extension in iOS 8.
In Objective-C, this code compiles and works:
[itemProvider loadItemForTypeIdentifier:(#"public.url" options:nil completionHandler:^(NSURL *url, NSError *error) {
//My code
}];
In Swift however, I'm getting "NSSecureCoding!' is not a subtype of 'NSURL" compile error when I try to do something similar:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: { (urlItem:NSURL, error:NSError!) in
//My code
})
If I add the bang to NSURL argument type as in NSURL! I get "Cannot convert the expression's type 'Void' to type 'Void'" compile error. And if I leave the default argument typed as NSSecureCoding!, it compiles, but the block/closure doesn't run.
What am I doing wrong?
You don't need to specify the types for urlItem or error as they can be inferred from the declaration of loadItemForTypeIdentifier:options:completionHandler. Just do the following:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler: {
(urlItem, error) in
//My code
})
Even better, you can move the closure outside of the method call:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
//My code
}
This API makes use of reflection internally: it looks at the type of the block’s first parameter to decide what to return. This is dependent on Objective-C’s looser enforcement of the block signature compared to Swift — and therefore does not work in Swift. (Yes, still.) See this discussion on the developer forums.
I recommend filing a bug report with Apple and writing small Objective-C wrapper methods to read each type of data you need.
It’s possible I’ve overlooked something. If someone has found a neat way to make this work in Swift I’m keen to hear it.
The new API to use is canLoadObject and loadObject, use it as this:
if (itemProvider.canLoadObject(ofClass: NSURL.self)) {
print("==== attachment is URL")
itemProvider.loadObject(ofClass: NSURL.self, completionHandler:
{
(data, error) in
print("==== url object = \(data)")
})
}
The same can be used for UIImage
https://developer.apple.com/documentation/uikit/drag_and_drop/data_delivery_with_drag_and_drop
Just in case you or other ones still need a solution, it is simple. Just add a side variable with a cast to NSURL. Here it is:
itemProvider.loadItemForTypeIdentifier("public.url", options: nil) {
(urlItem, error) in
let url : NSURL = urlItem : NSURL
// Whatever you like to do with your new url
}