Handle swift throw in closure in objective-c - ios

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

Related

Objective C - Called object type 'BOOL' (aka 'bool') is not a function or function pointer

Working on a legacy hybrid iOS project. Created one new Swift util class in ConsentManager.swift, like below,
import Foundation
public class ConsentManager: NSObject {
#objc static let sharedInstance = ConsentManager()
#objc private override init() {}
#objc public func isDataPermissionConsentRequired() -> Bool
{
…
return value; // based on logic
}
}
Called the method from another objc class, ConsentChecker.m like,
#interface ConsentChecker ()
{
}
#end
#implementation ConsentChecker
-(void)checkConsent {
// GETTING ERROR IN THE FOLLOWING LINE
if (ConsentManager.sharedInstance.isDataPermissionConsentRequired()) {
…
}
}
#end
Getting compiler error:
Called object type 'BOOL' (aka 'bool') is not a function or function pointer
Why and how to resolve it?
The reason you're hitting this is that methods in Objective-C which take no arguments may be called implicitly using dot syntax similar to Swift's, but not exactly like it. A method declared like
// Inside of SomeClass
- (BOOL)someMethod { /* return something */ }
can be called either as
SomeClass *instance = ...
// Traditional Obj-C syntax:
BOOL value = [instance someMethod];
or
// Dot syntax accessor:
BOOL value = instance.someMethod;
Note that the dot syntax version does not use parentheses to denote the call. When you add parentheses like you would in Swift, Obj-C determines that you are trying to call the returned value from the method as if it were a function:
instance.someMethod();
// equivalent to:
BOOL value = [instance someMethod];
value(); // <- Called object type 'BOOL' (aka 'bool') is not a function or function pointer
You cannot call a BOOL like you can a function, hence the error.
#Dávid offers the more traditional Obj-C syntax for calling this method, but alternatively, you can simply drop the parentheses from your call:
if (ConsentManager.sharedInstance.isDataPermissionConsentRequired) {
Objective-C-ism note:
Dot syntax is most idiomatically used for method calls which appear like properties (e.g. boolean accessors like your isDataPermissionConsentRequired), even if the method might need to do a little bit of work to return that value (think: computed properties in Swift).
For methods which perform an action, or which return a value but might require a significant amount of work, traditional method call syntax is typically preferred:
// Prefer:
[instance doTheThing];
NSInteger result = [instance performSomeExpensiveCalculation];
// over:
instance.doTheThing;
NSInteger result = instance.performSomeExpensiveCalculation;
The Obj-C syntax for executing methods is different from Swift's dot syntax.
This is the correct syntax:
if ([ConsentManager.sharedInstance isDataPermissionConsentRequired]) {
If u want to call swift function on obj-c class you use to obj-c syntax
Correct Syntax is:
if ([ConsentManager.sharedInstance isDataPermissionConsentRequired]) {
// Write logic here
}

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

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

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