I have a question on catching both Swift errors and Objective-C exceptions using a single do-catch statement.
Let's say I have a function which invokes a bunch of other functions that may either throw a Swift Error or an Objective-C NSException:
func performTasks() throws {
functionThrowingNSException()
try functionThrowingSwiftError()
functionThrowingNSException()
try functionThrowingSwiftError()
…
}
The Swift function may look somewhat like this:
func functionThrowingSwiftError() throws {
throw MyError.someError(message: "Swift error")
}
enum MyError: Error {
case someError(message: String)
}
And the Objective-C part may look like this:
func functionThrowingNSException() {
let myLibrary = MyLibrary()
myLibrary.performRiskyTask()
}
#interface MyLibrary : NSObject
- (void)performRiskyTask;
#end
#implementation MyLibrary
- (void)performRiskyTask {
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:#"Objective-C exception"
userInfo:nil];
}
#end
Now I would like to catch both types of exceptions in a single catch-block. If I try this, however …
do {
try performTasks()
} catch {
print("Error caught:", error)
}
…, the app crashes at runtime:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Objective-C exception'
As pointed out in a previous post, this issue can be resolved using the following code:
#interface ExceptionHandling : NSObject
+ (BOOL)tryVoidObjC:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;
#end
#implementation ExceptionHandling
+ (BOOL)tryVoidObjC:(void (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error {
#try {
tryBlock(error);
return error == NULL || *error == nil;
} #catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:#{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
#"callStackSymbols": exception.callStackSymbols
}];
}
return NO;
}
}
#end
extension ExceptionHandling {
static func tryVoidObjC(_ block: () throws -> Void) throws {
try __tryVoidObjC { (errorPointer: NSErrorPointer) in
do {
try block()
} catch {
errorPointer?.pointee = error as NSError
}
}
}
}
This way, I can convert the NSException to a Swift error and catch it:
do {
try performTasks()
} catch {
print("Error caught:", error)
}
func performTasks() throws {
try ExceptionHandling.tryVoidObjC {
functionThrowingNSException()
try functionThrowingSwiftError()
functionThrowingNSException()
try functionThrowingSwiftError()
}
}
Error caught: Error Domain=NSInternalInconsistencyException Code=-1 "Objective-C exception" UserInfo={callStackSymbols =(…), NSLocalizedDescription=Objective-C exception, NSUnderlyingError=Objective-C exception}
Now, the functionThrowingNSException() is a void function. What if I want to use an Objective-C method that can either throw an NSException or return a value? So something like this:
do {
let answer = try performTasks()
print("The answer is \(answer).")
} catch {
print("Error caught:", error)
}
func performTasks() throws -> Int {
return try ExceptionHandling.tryObjC {
functionThrowingNSException()
try functionThrowingSwiftError()
functionThrowingNSException()
try functionThrowingSwiftError()
return ultimateAnswer()
}
}
func ultimateAnswer() -> Int {
let myLibrary = MyLibrary()
return myLibrary.ultimateAnswer()
}
#interface MyLibrary : NSObject
- (NSInteger)ultimateAnswer;
#end
#implementation MyLibrary
- (NSInteger)ultimateAnswer {
#throw [NSException exceptionWithName:NSGenericException
reason:#"Computation timed out"
userInfo:nil];
}
#end
How would I go about doing that?
First approach:
It's important to understand how Swift translates Cocoa's error pattern:
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. Similarly, if an error producing Objective-C method returns a nil value to indicate the failure of a method call, Swift changes the return type of the function to a nonoptional type.
Otherwise, if no convention can be inferred, the method is left intact.
(https://developer.apple.com/documentation/swift/cocoa_design_patterns/about_imported_cocoa_error_parameters)
For this reason, the +[ExceptionHandling tryVoidObjC:error:] method (if it weren't "refined for Swift"), is translated to func tryObjC(_ tryBlock: ((NSErrorPointer) -> Void)!) throws.
We need to use a boolean return value for this Objective-C method, because if we were to use a void return type, no convention could be inferred and the method would instead be translated to the non-throwing function func tryObjC(_ tryBlock: ((NSErrorPointer) -> Void)!, error: NSErrorPointer).
Similarly, we cannot use an integer return type because this would translate the method to func tryObjC(_ tryBlock: ((NSErrorPointer) -> Int)!, error: NSErrorPointer) -> Int.
So one way to solve the problem would be to return an NSNumber instance and take care of converting it from and to an integer in the Swift refinement:
#interface ExceptionHandling : NSObject
+ (NSNumber *)tryObjC:(NSNumber * (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;
#end
#implementation ExceptionHandling
+ (NSNumber *)tryObjC:(NSNumber * (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error {
#try {
return tryBlock(error);
} #catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:#{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
#"callStackSymbols": exception.callStackSymbols
}];
}
return nil;
}
}
#end
extension ExceptionHandling {
static func tryObjC(_ block: () throws -> Int) throws -> Int {
let number = try __tryObjC { errorPointer in
do {
let integer = try block()
return NSNumber(integerLiteral: integer)
} catch {
errorPointer?.pointee = error as NSError
return nil
}
}
return number.intValue
}
}
This solution does actually work. The code will compile and, if those other methods don't interrupt execution by throwing errors …
func functionThrowingNSException() {
//let myLibrary = MyLibrary()
//myLibrary.performRiskyTask()
}
func functionThrowingSwiftError() throws {
//throw MyError.someError(message: "Swift error")
}
…, the exception will be caught:
Error caught: Error Domain=NSGenericException Code=-1 "Computation timed out" UserInfo={callStackSymbols=(…), NSLocalizedDescription=Computation timed out, NSUnderlyingError=Computation timed out}
Likewise, the actual return value is printed if the library method doesn't throw:
#implementation MyLibrary
- (NSInteger)ultimateAnswer {
return 42;
}
#end
The answer is 42.
This solution is not ideal, however, because it would need to be re-implemented for every return type. It would be preferable for the tryObjC method to have a generic return type.
The first step to dealing with generic return types is to replace NSNumber * by id in the Objective-C class:
#interface ExceptionHandling : NSObject
+ (id)tryObjC:(id (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error NS_REFINED_FOR_SWIFT;
#end
#implementation ExceptionHandling
+ (id)tryObjC:(id (NS_NOESCAPE ^)(NSError **))tryBlock error:(NSError **)error {
#try {
return tryBlock(error);
} #catch (NSException *exception) {
if (error != NULL) {
*error = [NSError errorWithDomain:exception.name code:-1 userInfo:#{
NSUnderlyingErrorKey: exception,
NSLocalizedDescriptionKey: exception.reason,
#"callStackSymbols": exception.callStackSymbols
}];
}
return nil;
}
}
#end
Next, provide a generic Swift method as the refinement:
extension ExceptionHandling {
static func tryObjC<T>(_ block: () throws -> T) throws -> T {
return try __tryObjC { errorPointer in
do {
return try block()
} catch {
errorPointer?.pointee = error as NSError
return nil
}
} as! T
}
}
The code will compile and the result will be printed (or the NSException will be caught):
do {
let answer = try performTasks()
print("The answer is \(answer).")
} catch {
print("Error caught:", error)
}
func performTasks() throws -> Int {
return try ExceptionHandling.tryObjC {
functionThrowingNSException()
try functionThrowingSwiftError()
functionThrowingNSException()
try functionThrowingSwiftError()
return ultimateAnswer()
}
}
The answer is 42.
The compiler even took care of "autoboxing" the NSInteger to an NSNumber * automatically.
Remark:
The +[ExceptionHandling tryObjC:error:] method is translated to func __tryObjC(_ tryBlock: ((NSErrorPointer) -> Any?)!) throws -> Any.
Notice that while the tryBlock seems to support an optional return value, the return value of the __tryObjC(_:) method must be non-optional. This is in line with the excerpt from the Swift docs saying that Swift changes the return type of the Objective-C method to a non-optional type.
If we actually were to supply nil as the return value of the tryBlock …
#interface MyLibrary : NSObject
- (NSString *)ultimateAnswer;
#end
#implementation MyLibrary
- (NSString *)ultimateAnswer {
return nil;
}
#end
func performTasks() throws -> String? {
return try ExceptionHandling.tryObjC {
functionThrowingNSException()
try functionThrowingSwiftError()
functionThrowingNSException()
try functionThrowingSwiftError()
return ultimateAnswer()
}
}
func ultimateAnswer() -> String? {
let myLibrary = MyLibrary()
return myLibrary.ultimateAnswer()
}
…, we would get a runtime error saying …
Could not cast value of type 'NSNull' to 'NSString'
… because the forced cast try __tryObjC { … } as! T would fail.
So returning nil but not throwing an NSException is not a pattern that is supported by this solution (just like returning nil but not setting the error parameter is not following Cocoa's error convention).
EDIT: Please understand the implications of this approach pointed out in the comments below.
I have a little bit misunderstanding how to use -(void) in swift correctly. Whenever I have tried to write a code using -(Void) but I am getting an error "Expected declaration" or sometimes "Binary operator '-' cannot be applied to operands of type 'UIFont' and '(Void).Type'".
Why is this happening? What is the right way to use this functionality correctly in swift?
{
- (Void)keyboardWillShow { //>> showing me the error "Binary operator '-' cannot be applied to operands of type 'UIFont' and '(Void).Type'"
// Animate the current view out of the way
if (self.view.frame.origin.y >= 0)
{
[self setViewMovedUp:YES];
}
else if (self.view.frame.origin.y < 0)
{
[self setViewMovedUp:NO];
}
}
}
Thanks
It sounds like you're trying to declare a function with no return value. In Swift this is done as:
func thisFunction() {
..
}
You can return nil value like below code:
func myFunc(myVar: String) -> ()? {
print(myVar)
return nil
}
var c = myFunc("Hello")
print(c) // nil
but Swift Function always return default value nil if not declared. like below code:
func myFunc(myVar: String) {
print(myVar)
return
}
var c = myFunc("Hello")
print(c) // ()
I want my YouTubePlayer State to change a public variable in my view controller. I'm having trouble with the YTPlayer Delegate function and the switch. Here is my var...
var ytps = String!
here is the youtube state function...
func playerView(player: YTPlayerView!, didChangeToState state: YTPlayerState)
{
switch (state)
{
case YTPlayerState.Ended:
// handle ended state
print("Ended")
break;
case YTPlayerState.Paused:
// handle paused state
print("Paused")
break;
default:
break;
}
}
How can I get the state of either YTPlayerState.Ended or YTPlayerState.Paused to change the value of my empty string to "Ended/Paused"?
I guess you could just replace print("Paused") and print("Ended") with ytps = "Paused" and ytps = "Ended". Full example:
var ytps = String()
func playerView(player: YTPlayerView!, didChangeToState state: YTPlayerState) {
switch state {
case YTPlayerState.Ended:
ytps = "Ended"
break
case YTPlayerState.Paused:
ytps = "Paused"
break
default:
break
}
}
I'm trying to do the equivalent in Objective-C:
if (causeStr != nil) {
...
}
I would get a compiler error if do this:
if !(let myString = causeStr) {
}
So I'm left with this:
if let myString = causeStr {
} else {
// ... do something
}
Is there a more-elegant way to do this?
Yes, there's a more elegant way, and coincidentally it's the same as in obj-c:
if myString == nil {
...
}
Note: Your 1st snippet of code should be:
if (causeStr == nil) {
...
}
otherwise it means I misunderstood your question...
I am getting error while converting my app to ARC.The error is :Implicit conversion of int to UItextfield is disallowed with ARC.How to cast them using toll free bridging?-
-(void)textFieldDidEndEditing:(UITextField *)textField {
//if (![textField isEqual:normal]) {
if (textField == numberKeyPad.currentTextField) {
[self.numberKeyPad removeButtonFromKeyboard];
}
if (numberKeyPad.currentTextField ==normal)
{
[self.numberKeyPad removeButtonFromKeyboard];
}
}
I am getting the error for if (numberKeyPad.currentTextField ==normal) line.How to resolve it?
You are comparing object (UITextField) with primitive type (int). Because of that compiler is throwing an error.
Try this -
-(void)textFieldDidEndEditing:(UITextField *)textField {
//if (![textField isEqual:normal]) {
if (textField == numberKeyPad.currentTextField) {
[self.numberKeyPad removeButtonFromKeyboard];
}
if ([((numberKeyPad.currentTextField).text) intValue] ==normal)
{
[self.numberKeyPad removeButtonFromKeyboard];
}
}