Call objc code that throws exception in Swift - ios

I have this code in ObjC/C:
AVCaptureFocusMode GetFocusModeWithString(NSString *mode) {
if ([mode isEqualToString:#"locked"]) {
return AVCaptureFocusModeLocked;
} else if ([mode isEqualToString:#"auto"]) {
return AVCaptureFocusModeAutoFocus;
} else {
#throw [NSError errorWithDomain: #"FocusError", code: 12];
}
}
It used to be working fine when calling from ObjC code. Now I am rewriting the caller side using swift. However, in swift, this code does not actually throw:
// does not work, swift thinks this function does not throw.
do {
try GetFocusModeWithString(#"auto")
}
Am I doing anything wrong here? Is the ObjC code bad? how can I improve the ObjC code to work nicely with swift?

Objective C does not have do/try/catch semantics the way that Swift does.
#throw causes a runtime exception. This isn't something you can catch in Swift.
The way that you can integrate Cocoa error handling with Swift is described in the Swift Objective C interoperability documentation and this answer
First declare your Objective-C function to accept a pointer to an instance of NSError. Then you can flag that parameter with function with __attribute__((swift_error(nonnull_error))).
This will cause a Swift exception if the error is non-null when the function returns
- (AVCaptureFocusMode) getFocusModeWithString:(NSString *) mode error: (NSError **)error __attribute__((swift_error(nonnull_error))); {
*error = nil;
if ([mode isEqualToString:#"locked"]) {
return AVCaptureFocusModeLocked;
} else if ([mode isEqualToString:#"auto"]) {
return AVCaptureFocusModeAutoFocus;
}
*error = [NSError errorWithDomain:#"FocusError" code:12 userInfo:nil];
return -1;
}
Now, in your Swift code you can call it with
do {
let focusMode = try getFocusMode(with: "auto")
} catch {
print(error)
}
Note that this will change your method signature in Objective-C; You will need to pass &error to the method and check its value on return.

Objective-C exceptions are not compatible with Swift. Here's what Apple says:
Handle Exceptions in Objective-C Only
In Objective-C, exceptions are distinct from errors. Objective-C exception handling uses the #try, #catch, and #throw syntax to indicate unrecoverable programmer errors. This is distinct from the Cocoa pattern—described above—that uses a trailing NSError parameter to indicate recoverable errors that you plan for during development.
In Swift, you can recover from errors passed using Cocoa’s error pattern, as described above in Catch Errors. However, there’s no safe way to recover from Objective-C exceptions in Swift. To handle Objective-C exceptions, write Objective-C code that catches exceptions before they reach any Swift code.

Related

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

Catch an objectiveC exception that was thrown as a result of a javascript script

Is there any way of catching an Objective-C exception that was thrown as a result of a JavaScript script evaluation?
For example:
I have a class Obj with a method canThrow that I have exported through JSExport. From some script I call this method and it indeed throws an exception.
Any way I can handle it?
I have already tried to wrap the script evaluation code in a try-catch, but that didn't help.
Your question is a little bit unclear, but I will try to answer it anyway.
If you need to raise an exception from Objective-C to Javascript, you should use -[JSContext exception] property. See following question for details.
Passing exception from Javascript to Objective-C is straightforward, you simply export some method, that will handle exception in Objective-C like:
myJSContext[#"PassExceptionToObjC"] = ^void(JSValue *)jsException {
// Handle exception
}
And then use following code in Javascript:
try {
// some code
}
catch (exception) {
PassExceptionToObjC(exception);
}
Alternatively you can return specific value from your Javascript to Objective-C. Like:
function doSomething() {
try {
// Do something
return 'Ok';
}
catch (error) {
return 'Error happens ' + error.message;
}
}
In case you need to avoid throwing Objective-C exception in method, called from Javascript, you simply should add #try/#catch in your Objective-C method or block:
myJSContext[#"DoSomething"] = ^void(JSValue *)someValue {
#try {
// Do something
}
#catch (NSException *exception) {
// Handle exception
}
}
Or, preferred, change logic of Objective-C code, that trigger exception.
Similar way applies if you need to avoid exception in Javascript, called from Objective-C. You simply add try/catch. In most cases you may simply ignore exceptions in JavaScript, called from Objective-C. But I suggest to implement exception handing on Objective-C side for any non-trivial Javascript, at least to simplify troubleshooting.
Hope it helps.

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

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

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)")
}

Resources