Completion closure within a closure - swift

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)

Related

if statements and optionals in Swift

Is there a difference between something like
if let error = error {
print(error.localizedDescription)
}
and just checking if it is = nil
if error != nil {
print(error.localizedDescription)
}
if I want to check if error has a value? Think of firebase's creating user function.
Yes.
The if let statement allows you to bind the value of error to a variable if it is non-nil and use it in the block. If maybeError is of type Error?, when you do:
if let error = maybeError {
/* block contents */
}
the type of error will be Error within the block - ie: It won't be an optional anymore. If you just do a nil-check using if, error will still be of type Error? within the block. So the code that would actually be equivalent to your first snippet would be:
if error != nil {
print(error!.localizedDescription)
}
(Your second snippet, as it is, won't compile, as you're trying to get the localizedDescription variable of an Error? object, which has no such property)
By the way, in case you haven't seen it before, the !. thing is the unwrap operator. It runs the method on the object if the object is non-nil, but it crashes in case the object is nil. In this case, you generally know it won't crash. (But this might not actually be safe depending on where and how you use it - check #rmaddy's comment)
In the first, error is now a non-optional type, so you can use .. This is also idiomatic -- it more clearly shows what you are trying to do.
In the second, you would need to use ?. to look at error's properties (your code won't compile because you haven't done that).

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.

How to avoid returning an optional after throwing an exception?

I'm writing a utility function which takes a parameter and always returns a valid non-nil result (notice the returned value is not optional because the possible parameters are all actually hardcoded and valid, so I know the function cannot fail):
func myFunction(param: String) -> NonTrivialObject {...}
Now, during development I want to experiment with possible parameters and, should I make a mistake, I want the function to throw an exception and just crash. I don't want or need to throw Swift errors or catch them, I want to hard-crash and fix the parameter immediately. In Objective C I would just use NSParameterAssert() or do something along these lines:
guard let validatedParam = param where param != nil else {
NSException(...).raise()
return nil
}
// do the actual work and return a non-optional result
However, I cannot return nil because the result is not an optional. Is there a way to somehow tell the compiler that it doesn't need to bother returning anything from the function after an exception is thrown? Or am I doomed to litter my code with unwrapping optionals or try! statements or to return a dummy object just to make the compiler pleased?
You can use Swift assert(_:_file:line:) function as follows
assert(some condition, "Message to display if condition is false (optional)" )
If the condition is verified the app will continue running, otherwise it will terminate
An optional may contain nil, but Swift syntax forces you to safely deal with it using the ? syntax to indicate to the compiler you understand the behavior and will handle it safely.
You can define the function to be a throwing function:
func foo(param: String) throws -> NonTrivialObject {
guard param != nil else {
throw SomeErrorEnum.NilFound
}
doStuff(...)
}
The error enum needs to conform to the ErrorType protocol. The function call is now able to catch errors like this:
do {
try foo(param)
} catch SomeErrorEnum.NilFound {
print("Found nil")
}
In this way the function returns a non-optional but can also throw errors. For more information see: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html

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

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.)

Swift parse framework and closures

This is srsly driving me crazy.
I am trying to use getFirstObjectInBackgroundWithBlock() method in swift but I can't figure out how to (not) use the optionals ..
I just want to get the user's score from the parse server And I do it like this:
func updateScoreForCurrentUser(score: Int){
let user = PFUser.currentUser()
// get gameScore for user
var query = PFQuery(className: "GameScore")
query.whereKey("User", equalTo: user!)
query.getFirstObjectInBackgroundWithBlock { (gameScore: PFObject, error: NSError?) -> Void in
gameScore["score"] = score
}
I just get a "Cannot invoke 'getFirstObjectInBackgroundWithBlock' with an argument list of type '((PFObject?, NSError?) -> Void)'"
Can you pleaase help me? Thank you
This error that you are getting is, as you've guessed already that you need to hvae gameScore object as an optional.
"Cannot invoke 'getFirstObjectInBackgroundWithBlock' with an argument
list of type '((PFObject?, NSError?) -> Void)'"
This is not because of swift or its limitations. It's because Parse SDK defines that function like that. And unless Parse changes its API, you will have to use an optional.
And just my two cents on the matter, an Optional is in order here.
Either you will get a PFObject or you will get an Error, not both. So one of them will be nil, hence the use of Optional.