Swift doesn't convert Objective-C NSError** to throws - ios

I have some Objective-C legacy code, that declares method like
- (void)doSomethingWithArgument:(ArgType)argument error:(NSError **)error
As written here https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html
Swift automatically translates Objective-C methods that produce errors
into methods that throw an error according to Swift’s native error
handling functionality.
But in my project described methods are called like this:
object.doSomething(argument: ArgType, error: NSErrorPointer)
Moreover, it throws runtime exception when I try to use them like:
let errorPtr = NSErrorPointer()
object.doSomething(argumentValue, error: errorPtr)
Do I need something more to convert Objective-C "NSError **" methods to Swift "trows" methods?

Only Objective-C methods are translated to throwing Swift methods, which do return a BOOL (not lower-cased bool), or a nullable-object.
(Tested with Xcode 11.7, and Swift 5 language.)
The reason is that Cocoa methods always use a return value NO or nil
to indicate the failure of a method, and not just set an error object.
This is documented in
Using and Creating Error Objects:
Important: Success or failure is indicated by the return value of the method.
Although Cocoa methods that indirectly return error objects in the Cocoa error
domain are guaranteed to return such objects if the method indicates failure
by directly returning nil or NO, you should always check that the return
value is nil or NO before attempting to do anything with the NSError object.
For example, the Objective-C interface
#interface OClass : NSObject
NS_ASSUME_NONNULL_BEGIN
-(void)doSomethingWithArgument1:(int) x error:(NSError **)error;
-(BOOL)doSomethingWithArgument2:(int) x error:(NSError **)error;
-(NSString *)doSomethingWithArgument3:(int) x error:(NSError **)error;
-(NSString * _Nullable)doSomethingWithArgument4:(int) x error:(NSError **)error;
-(BOOL)doSomething:(NSError **)error;
NS_ASSUME_NONNULL_END
#end
is mapped to Swift as
open class OClass : NSObject {
open func doSomethingWithArgument1(x: Int32, error: NSErrorPointer)
open func doSomethingWithArgument2(x: Int32) throws
open func doSomethingWithArgument3(x: Int32, error: NSErrorPointer) -> String
open func doSomethingWithArgument4(x: Int32) throws -> String
open func doSomething() throws
}
If you can change the interface of your method then you should add a boolean
return value to indicate success or failure.
Otherwise you would call it from Swift as
var error : NSError?
object.doSomethingWithArgument(argumentValue, error: &error)
if let theError = error {
print(theError)
}
Remark: At
https://github.com/apple/swift/blob/master/test/Inputs/clang-importer-sdk/usr/include/errors.h
I found that Clang has an attribute which forces a function to throw an error in Swift:
-(void)doSomethingWithArgument5:(int) x error:(NSError **)error
__attribute__((swift_error(nonnull_error)));
is mapped to Swift as
public func doSomethingWithArgument5(x: Int32) throws
and seems to work "as expected". However, I could not find any official documentation
about this attribute, so it might not be a good idea to rely on it.

You need to make your method return a BOOL, to tell the runtime that an error should or should not be thrown. Also you should add __autoreleasing to the error parameter, to make sure ARC doesn't accidentally release the error before you have a chance to use it:
- (BOOL)doSomethingWithArgument:(ArgType)argument error:(NSError * __autoreleasing *)error
You can then call it from Swift like this:
do {
object.doSomethingWithArgument(someArgument)
} catch let err as NSError {
print("Error: \(err)")
}

Related

Evaluating if Error is NSError always succeeds while casting Error as NSError gives a compiler error

We had an issue with an Error object that crashes in Crashlytics' Objective-c code because it doesn't respond to userInfo (a member of NSError), while investigating I stumbled upon this weird behavior in Swift.
In a playground, I tried creating a class SwiftError implementing the Error protocol:
class SwiftError: Error {
}
let sError = SwiftError()
if sError is NSError { // Generates a warning: 'is' test is always true
print("Success")
} else {
print("Fail")
}
// Prints Fail
let nsError = sError as NSError
//Compiler Error: 'SwiftError' is not convertible to 'NSError'; did you mean to use 'as!' to force downcast?
Checking if the SwiftError is NSError gives a warning that it always succeeds but fails runtime.
Casting SwiftError as an NSError gives a compiler error.
Can someone help explain to me why this happens and how could I know if a class implementing the Error protocol actually is an NSError or not ?
Thanks!
There is something odd about the nature of the bridging between NSError and Error. They are bridged for communication purposes — that is, they can travel back and forth between Swift and Cocoa; and an error that comes from Cocoa is an NSError (and NSError adopts the Error protocol in Swift, to allow for this); but your class that you declare as conforming to Error is itself not an NSError.
because it doesn't respond to userInfo
If you need your Swift Error type to carry userInfo information for Cocoa's benefit, then you were looking for the CustomNSError protocol.
https://developer.apple.com/documentation/foundation/customnserror
This looks like a bug to me, I have filed it as SR-14322.
According to SE-0112 Improved NSError Bridging,
Every type that conforms to the Error protocol is implicitly bridged to NSError.
This works with struct and enum types, but apparently not with class types.
You can replace your class SwiftError: Error by a struct SwiftError: Error to solve the problem. If that is not possible for some reason then the following “trick” worked in my test:
let nsError = sError as Error as NSError
That compiles and gives the expected results:
class SwiftError: Error {}
extension SwiftError: CustomNSError {
public static var errorDomain: String { "MyDomain" }
public var errorCode: Int { 13 }
public var errorUserInfo: [String : Any] { ["Foo" : "Bar" ] }
}
let sError = SwiftError()
let nsError = sError as Error as NSError
print(nsError.userInfo)
// Output: ["Foo": "Bar"]

inconsistency in NSManagedObjectContext.fetch(_:) method documentation

In the documentation for NSManagedObjectContext.fetch(_:), in the method definition, there is only one parameter request, but in the description section, it says there are two parameters request and error. Why the difference?
It is a result of how Objective-C and Swift interoperate. A common pattern in Objective-C is for a function to modify an Error reference if there was a problem, and if you look at the Objective-C declaration of that function you see:
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request
error:(NSError * _Nullable *)error;
With the introduction of try/throw/catch in Swift, the pattern that has been adopted is for functions declared in that way to throw the Error rather than use a side-effect and modify a parameter.
What meant by error is that before fetch line you specify a try catch block , that will catch the error (thrown) it's not a parameter so you have to wrap it insdie the do-catch , if you tried to remove try it will give a compile error
do {
let result:[People] = try managedContext.fetch(request)
}
catch let error as NSError
{
print(error)
}

Handle swift throw in closure in objective-c

I've an objective-c function that looks like below
+ (void) invokeSortingWithClassName: (NSString*) className functionName: (NSString*) functionName closure: (id(^)(NSArray* arr))closure;
and I've to call this method from a swift class. The swift code that calls this method looks like below
SortHandler.invokeSorting(withClassName: className, functionName: functionName, closure:
{
args in
let something = unwrap(args!)
do
{
let returnValue = try closure(something as NSArray!) //this is another closure coming as a parameter in the function in which this code is written and this throws
return returnValue;
}
catch
{
throw error
}
return "-1" as! T
})
At the start of closure definition, I get this attached error
Essentially, this closure throws an error and I am not sure, how to handle this error in objective-c definition of this function. Please help me fix this error
While a closure can be declared with throws, this closure has not been declared with throws, so your closure cannot throw - this is exactly what the error message is telling you.
Since the function is declared in Objective-C, the function signature can't be changed to include throws as Objective-C doesn't know anything about Swift exceptions.
The standard way in which error handling is translated between Swift and Objective-C is for the Objective-C block to receive an &NSError parameter.
If you have the ability to change the Objective-C method signature, it should be declared as
+(void) invokeSortingWithClassName: (NSString* _Nullable) className functionName: (NSString* _Nullable) functionName closure: (id _Nullable (^_Nullable)(NSArray* _Nullable arr, NSError* _Nullable * _Nullable error))closure;
This will allow you to throw from swift with the error being received via the error parameter in Objctive-C.
If you cannot change the method signature, then you will need to catch the exception in your closure and simply return an 'error' value, such as nil

How to write swift 3 method with throws statement and return value that can be used in Objective C?

Below Swift 3 method is translated into Objective C like:
func doSomething(param: String) throws // Swift 3
- (BOOL)doSomething:(NSString * _Nonnull)param error:(NSError * _Nullable * _Nullable)error; // Translated Objective C
Then, how can I write a method with both throws and a return type?
func doSomething(param: String) throws -> Int // Swift 3
// Error: Never translated into Objective C
I know flow should not be handled by NSError object. It just contains information about an error. And that's why there's a BOOL return type to let us know the invocation is succeeded without any problem.
Then how can I handle a method with both throws statement and return type? Is there a standard way to handle this?
Thank you in advance.
The standard way of reporting success or failure in Objective-C is
to return the boolean value NO or a nil object pointer,
as documented in
Using and Creating Error Objects:
Important: Success or failure is indicated by the return value of the method.
Although Cocoa methods that indirectly return error objects in the Cocoa error
domain are guaranteed to return such objects if the method indicates failure
by directly returning nil or NO, you should always check that the return
value is nil or NO before attempting to do anything with the NSError object.
So you can return a instance of an object
func doSomething(param: String) throws -> NSNumber
which is translated to
- (NSNumber * _Nullable)doSomethingWithParam:(NSString * _Nonnull)param error:(NSError * _Nullable * _Nullable)error;
and returns nil if an error is thrown, or return a boolean and
pass other values back by reference
func doSomething(param: String, value: UnsafeMutablePointer<Int>) throws
which is mapped to
- (BOOL)doSomethingWithParam:(NSString * _Nonnull)param value:(NSInteger * _Nonnull)value error:(NSError * _Nullable * _Nullable)error;
Try this:
- (int) doSomething:(NString *)param error:(NSError * _Nullable *)error;
There is another way to handle this if you can modify the throwable object.
If you can put up an enum for the errors, and annotate the error enum with #objc than the automatic bridge header will convert it to NSError like mentioned here before.
For example:
#objc enum MyError:Int, Error{
case AnError
case AnotherError
}
And your throwing swift method:
public class MyClass:NSObject{
public func throwAnError() throws {
throw MyError.AnotherError
}
public func callMe(){
print("Someone called!")
}
}
Will be converted by the auto bridging process to:
SWIFT_CLASS("_TtC13ErrorHandling7MyClass")
#interface MyClass : NSObject
- (BOOL)throwAnErrorAndReturnError:(NSError * __nullable * __null_unspecified)error;
- (void)callMe;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
Taken from the bottom of this excellent blog post which is relevant to this question.

Synchronous Save in Parse using Swift

Using Swift 2.1 (Xcode 7.2.) and Parse 1.12.0, I'm getting an error that PFObject.save() is unavailable in Swift. My code is:
let operation = NSBlockOperation { () -> Void in
do {
let success = try rating.save()
}
catch let er as NSError {
error = er
}
}
In PFObject+Synchronous.h, there is this:
///--------------------------------------
#pragma mark - Saving Objects
///--------------------------------------
/**
*Synchronously* saves the `PFObject`.
#return Returns whether the save succeeded.
*/
- (BOOL)save PF_SWIFT_UNAVAILABLE;
/**
*Synchronously* saves the `PFObject` and sets an error if it occurs.
#param error Pointer to an `NSError` that will be set if necessary.
#return Returns whether the save succeeded.
*/
- (BOOL)save:(NSError **)error;
So it seems as if Xcode can't tell which function to use: it should try to use the one that handles the error. Is there a way of forcing this, or am I calling the function incorrectly?
Although the function not marked as unavailable to swift:
-(BOOL)save:(NSError **)error
is defined to return a bool, the Swift implementation (which throws) apparently does not, so the code compiles fine if I'm not expecting to receive a return value, i.e.:
let operation = NSBlockOperation { () -> Void in
do {
try rating.save()
}
catch let er as NSError {
error = er
}
}
I'm still now sure how I could have determined this without trial and error.
The first overload is marked unavailable to Swift, hence it is not visible.
The second overload is made available, but as you discovered yourself, it requires a try since it returns a NSError output parameter. The BOOL return value in Cocoa is there to indicate whether the operation was successful or not. In Swift, this is handled by catching the NSError instead. This behaviour was introduced in (I think) Swift 2.0, and is documented here.
To summarize, an Obj-C method
- (BOOL) doSomething:(NSError**)error {}
maps to the following Swift method
func doSomething() throws

Resources