0

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?

OMGPOP
  • 1,995
  • 8
  • 52
  • 95
  • 1
    `@throw` in Objective-C throws an [_exception_](https://developer.apple.com/documentation/foundation/nsexception?language=objc), not an error — these cannot be caught in Swift directly. See https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift for more info. – Itai Ferber Jun 22 '22 at 01:59
  • @ItaiFerber should this situation in my code be an exception or an error? – OMGPOP Jun 22 '22 at 01:59
  • It depends entirely on how you expect error handling to work. An exception indicates serious programmer error and that the program should crash (which is the default behavior). Swift error handling is interactive, and does not necessarily indicate programmer error — most Swift errors indicate some sort of runtime error. If you were writing `GetFocusModeWithString` in Swift, it's likely that you would just make the return value optional, and return `nil` if the input was unknown. – Itai Ferber Jun 22 '22 at 02:02
  • That being said, if you consider an invalid input to this function to be serious programmer error, then an exception could be considered valid, and crashing may be appropriate (similar to `fatalError` in pure Swift). – Itai Ferber Jun 22 '22 at 02:02
  • Should I convert it to `@throw NSException` instead? – OMGPOP Jun 22 '22 at 02:03
  • Just tried changing to NSException and does not help – OMGPOP Jun 22 '22 at 02:04
  • No, you need to avoid `@throw` altogether if you expect to be able to handle it in Swift. In order to have this function appear as `throws` in Swift, you'd need to rewrite it into a different format altogether. Is it feasible to rewrite this function as an extension on `AVCaptureFocusMode` in Swift itself, which returns an `AVCaptureFocusMode?`? Then you could return `nil` for unknown strings. – Itai Ferber Jun 22 '22 at 02:05
  • Do you recommend to have this function to return BOOL to indicate success/failure, and have (NSError **) and (AVCaptureMode *) pointers passed in? – OMGPOP Jun 22 '22 at 02:05
  • That's one possibility if you absolutely must keep this function written in Objective-C, yes, though rewriting in pure Swift is likely going to be simpler and easier. I would recommend writing in Swift if possible. – Itai Ferber Jun 22 '22 at 02:06
  • @ItaiFerber I think it's better to encapsulate the error reason inside this function? – OMGPOP Jun 22 '22 at 02:07

2 Answers2

1

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.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
0

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.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • What should i do in this case? – OMGPOP Jun 22 '22 at 02:01
  • 1
    Is there some reason not to write the function in Swift? – rob mayoff Jun 22 '22 at 02:05
  • This function is already there, and owned by another team. Unfortunately I have to leverage language inter-op for this – OMGPOP Jun 22 '22 at 02:07
  • The function is tiny. Can you just write your own version in Swift and not tell anyone? – rob mayoff Jun 22 '22 at 02:09
  • @OMGPOP If the function itself cannot change and you must use it, then you will have to write a wrapper function in Objective-C which catches the exception and returns you something meaningful instead — you will not be able to catch a thrown object in Swift. – Itai Ferber Jun 22 '22 at 02:10
  • haha i wish i could. but i have to use this objc function :(. – OMGPOP Jun 22 '22 at 02:10
  • oh i can surely refactor this function, it's just that i can't convert their project to swift – OMGPOP Jun 22 '22 at 02:11
  • I think i agree that it's bad objc code - they should throw exceptions, not errors. That needs to be changed (i guess?). but i can only use objc for their code. – OMGPOP Jun 22 '22 at 02:11
  • @OMGPOP To be clear, even if the code threw exceptions, that would not help you: you need to refactor this function to avoid `@throw` _at all_. So you need to refactor to write to out-parameters safely. – Itai Ferber Jun 22 '22 at 02:13
  • do you think this kind of code should be an exception or an error? maybe i shouldn't try/catch it in the first place? it's just that their original objc caller have `@try` and `@catch`, and i am trying to mimic the behavior, but maybe that's wrong in the first place? – OMGPOP Jun 22 '22 at 02:15
  • @OMGPOP Honestly, neither. Errors are meant for user-visible errors that you couldn't anticipate at runtime. There really isn't anything meaningful you could put in an `NSError` here that would make sense. I'd change this method to return a `BOOL` to indicate whether the parameter was valid, and write to an `AVCaptureFocusMode *` out-parameter with the actual result. – Itai Ferber Jun 22 '22 at 02:19