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

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)

Related

removePersistentStore(_:) doesn't return Bool in Swift

I'm trying to store the return value of removePersistentStore(_:) which is clearly a Bool according to the documentation:
I'm using it like that:
let removed = try! storeCoordinator.removePersistentStore(store)
But get an alert that removed is just Void:
What's the deal with this? Am I missing something?
Return Value
YES if the store is removed, otherwise NO.
in the NSPersistentStoreCoordinator documentation refers to the Objective-C signature of
the method:
- (BOOL)removePersistentStore:(NSPersistentStore *)store
error:(NSError * _Nullable *)error
In Swift, the method throws an error instead of returning a boolean:
func removePersistentStore(_ store: NSPersistentStore) throws
so that you can call it as
try! storeCoordinator.removePersistentStore(persistentStore)
or better:
do {
try storeCoordinator.removePersistentStore(persistentStore)
} catch let error as NSError {
print("failed", error.localizedDescription)
}
Compare Error Handling in the "Using Swift with Cocoa and Objective-C" reference:
If the last non-block parameter of an Objective-C method is of type NSError **, Swift replaces it with the throws keyword, to indicate that the method can throw an error.
...
If an error producing Objective-C method returns a BOOL value to indicate the success or failure of a method call, Swift changes the return type of the function to Void.

Error message: initialiser for conditional binding must have optional type, not '()'

I am getting error:
initialiser for conditional binding must have optional type, not '()'.
Using Swift language, below is the code:
if let result = brain.performOperation(operation)
I think I can answer your question, but I don't know how much it will help you.
The way if let someVar = someOptional { } is used is to check the value of a variable (someOptional) to see if it is nil or a "value". If it is not nil then someVar will be assigned the non-nil value of someOptional and execute the code between { } which can safely reference someVar, knowing it is not nil. If someOptional is nil then the code within { } is bypassed and not executed.
The comment you posted above indicates that the performOperation() method is this:
func performOperation(symbol:String) {
if let operation = knownOps[symbol] {
opStack.append(operation)
}
}
This method does not return anything, or more formally it returns void aka (). void, or () is not a value, nor is it nil.
So when you have this statement
if let result = brain.performOperation(operation) { }
the compiler complains because it expects brain.performOperation(operation)
to return either nil or a value, but not void aka () which is exactly what the method returns.
If you are still confused about optionals, be sure to read as much as you can of the Swift Language Reference. Optionals are a big part of the language and once you get used to them you will find them very invaluable.

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

Getting a function's return type in Swift

I realize that reflection isn't fully supported (yet) in Swift, but reflection run time methods are (apparently) supported. I'm trying to get the return type of a function at run time. Here's my example
let s:Selector = "willAnimateRotation"
var m:Method = class_getInstanceMethod(object_getClass(self), s)
let returnType = method_copyReturnType(m)
println("method: \(m); returnType: \(returnType)")
free(returnType)
Here's an example of my willAnimateRotation method, currently returning String:
private func willAnimateRotation() -> String {
return "abc"
}
The output of this does not seem to vary depending on the return type of the selector. E.g., with String or Void return type for the selector, I get the following output:
method: 0x0000000000000000; returnType: 0x0000000000000000
Thoughts?
ALSO: I'm actually not really trying to do this in Swift. I'm bridging an Objective-C class to Swift, and am getting the same results there, when the Objective-C code attempts to determine the return type of a Swift selector. That is, my end-goal in this case happens to be to use Objective-C to get the return type of a Swift selector.
OK. I've figured this out. The problem comes with my use of the "private" keyword. If you drop that and just use:
func willAnimateRotation() -> String {
return "abc"
}
The sample code above works fine.
In Swift you indicate the function’s return type with the return arrow -> (a hyphen followed by a right angle bracket), which is followed by the name of the type to return.

Swift - 'Bool' is not a subtype of 'Void'?

I'm getting the following error: 'Bool' is not a subtype of 'Void'
performBlock takes a void closure with no argument, and method itself has a single argument, so I should be able to use the following syntax for my closure. Why am i getting this compile error?
workingManagedObjectContext.performBlock {
self.workingManagedObjectContext.save(nil)
self.managedObjectContext.performBlock {
self.managedObjectContext.save(nil)
}
}
The argument to performBlock is a closure taking no arguments and returning Void
(i.e. no return value).
If the closure consists of a single expression, the return type is inferred from
the type of that expression. The type of
self.managedObjectContext.save(nil)
is Bool, which cannot implicitly be converted to Void.
To fix that problem, you can add an explicit return statement:
self.managedObjectContext.performBlock {
self.managedObjectContext.save(nil)
return
}
or (better), check the return value of the save operation instead of ignoring it:
self.managedObjectContext.performBlock {
var error : NSError?
if !self.managedObjectContext.save(&error) {
// report error
}
}
(and do the same for the outer level save).
Update: As of Swift 1.2 (Xcode 6.3), unannotated single-expression closures with non-Void return types can now be used in Void contexts. So this does now compile without errors:
self.managedObjectContext.performBlock {
self.managedObjectContext.save(nil)
// explicit "return" not needed anymore in Swift 1.2
}
(Of course it is still better to actually check the return value
from the save operation.)