19

I am trying to set the value of an @objc object in my Swift code using obj.setValue(value, forKey: key).

It works fine when the object has the property being set. But if it doesn't, my app crashes hard with an uncaught NSException ("class is not key value coding-compliant…").

How can I catch and absorb this exception like I can in Objective-C so as to not crash my app? I tried wrapping it in a Swift try-catch, but it complains that none of the instructions throws and does nothing.

devios1
  • 36,899
  • 45
  • 162
  • 260
  • Do you really need KVC? Objects in dictionaries are supposed to be set with `setObject:forKey:` or key subscripting especially in Swift. – vadian Jan 31 '16 at 21:20
  • Yes, I'm writing an XML parser and need to be able to set arbitrary properties on objects by their names. – devios1 Jan 31 '16 at 21:21
  • Then rather than catch an exception check if the key exists. – vadian Jan 31 '16 at 21:22
  • That would totally work. I didn't think there was a way to do that though! – devios1 Jan 31 '16 at 21:23
  • 2
    Possible duplicate of [Catching NSException in Swift](https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift) – kelin Mar 23 '19 at 13:22

4 Answers4

24

see this answer:

//
//  ExceptionCatcher.h
//

#import <Foundation/Foundation.h>    

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}
Community
  • 1
  • 1
Casey
  • 6,531
  • 24
  • 43
15

Swift 5 version with utility error type:

NS_INLINE NSException * _Nullable ExecuteWithObjCExceptionHandling(void(NS_NOESCAPE^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}
public struct NSExceptionError: Swift.Error {

   public let exception: NSException

   public init(exception: NSException) {
      self.exception = exception
   }
}

public struct ObjC {

   public static func perform(workItem: () -> Void) throws {
      let exception = ExecuteWithObjCExceptionHandling {
         workItem()
      }
      if let exception = exception {
         throw NSExceptionError(exception: exception)
      }
   }
}

Vlad
  • 6,402
  • 1
  • 60
  • 74
13

Not the answer I was hoping for, unfortunately:

Although Swift error handling resembles exception handling in Objective-C, it is entirely separate functionality. If an Objective-C method throws an exception during runtime, Swift triggers a runtime error. There is no way to recover from Objective-C exceptions directly in Swift. Any exception handling behavior must be implemented in Objective-C code used by Swift.

Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C (Swift 2.1).” iBooks. https://itun.es/ca/1u3-0.l

My next plan of attack is to add an Objective-C function I can call out to that will wrap the attempt in @try/@catch. This really sucks, Apple.

Community
  • 1
  • 1
devios1
  • 36,899
  • 45
  • 162
  • 260
1

I wanted to call out the answer given in this question:

Catching NSException in Swift

as you can still use the Swift do/catch with the call in a natural fashion, the next best thing to Swift actually catching ObjC exceptions as it should.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150