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
Related
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)
}
I see some methods throw out errors in Apple's Documentation. But I can't find any information about what it throws.
Like this methods below. It's in FileManager class.
func moveItem(at srcURL: URL, to dstURL: URL) throws
I want to know what kinds of errors it throws. Where can I get related information?
Unlike Java, where the throws declaration requires a type, in Swift you will not know, what type of Error will be thrown. The only thing you know is that the object conforms to the Error-protocol.
If you know that a function throws a certein Error (because it's well documented), you will need to cast the caught object properly.
Example:
do {
try moveItem(from: someUrl, to: otherUrl)
} catch {
//there will automatically be a local variable called "error" in this block
// let's assume, the function throws a MoveItemError (such information should be in the documentation)
if error is MoveItemError {
let moveError = error as! MoveItemError //since you've already checked that error is an MoveItemError, you can force-cast
} else {
//some other error. Without casting it, you can only use the properties and functions declared in the "Error"-protocol
}
}
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
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.
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)")
}