2

Crashlytics reports that the following line is sometimes throwing a NSInternalInconsistencyException:

let attrStr = try NSMutableAttributedString(
        data: modifiedFont.data(using: String.Encoding.unicode, 
        allowLossyConversion: true)!,
        options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue],
        documentAttributes: nil)

Here I'm not as interested in why this happens (there's a 3 year old question about it) as I am in catching/handling this exception. I've tried to do it like this:

do {
    let attrStr = try NSMutableAttributedString(
       data: modifiedFont.data(using: String.Encoding.unicode, allowLossyConversion: true)!,
       options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue],
       documentAttributes: nil)

     self.attributedText = attrStr
} catch {
    self.attributedText = nil
    self.text = text.stripHTML()
}

... but this is not working for some reason - the exception is still being reported.

Am I trying to catch it in the right way? Can it be caught at all? If not, what are my options?

TimSim
  • 3,447
  • 7
  • 39
  • 78
  • 4
    Swift has Error Handling not Exception Handling. You can't can't catch an Exception with Swift; you just have to avoid it. Some of the links you provided implied that this call shouldn't be performed on a background thread. – vacawama Jun 27 '19 at 20:55
  • https://stackoverflow.com/a/28916420/1630618 – vacawama Jun 27 '19 at 20:59
  • https://stackoverflow.com/questions/32758811/catching-nsexception-in-swift – Sulthan Jun 28 '19 at 21:54

3 Answers3

2

Swift converts Objective-C methods with nullable returns and trailing NSError** parameters to methods that throw in Swift. But, in Objective-C, you can also throw exceptions. These are distinct from NSErrors and Swift does not catch them. In fact there is no way to catch them in Swift. You would have to write an Objective-C wrapper that catches the exception and passes it back in some way Swift can handle.

You can find this in the Apple document Handling Cocoa Errors in Swift in the Handle Exceptions in Objective-C Only section.

So it turns out that you can catch it, but it is worthwhile considering whether you should (see comments from @Sulthan below). To the best of my knowledge most Apple frameworks are not exception-safe (see: Exceptions and the Cocoa Frameworks) so you can't just catch an exception and continue on as if nothing happened. Your best bet is save what you can and exit as soon as possible. Another issue to consider is that unless you rethrow the exception, frameworks such as Crashlytics won't report it back to you. So, if you did decide to catch it you should log it and/or rethrow it so that you know that it is happening.

idz
  • 11,341
  • 1
  • 26
  • 37
  • 1
    In summary, it's not advised to catch Objective-C exceptions because their propagation often leaves objects in invalid state and you cannot successfully recover from them. – Sulthan Jun 27 '19 at 21:29
  • Yes, but you can do your best to exit gracefully (e.g. save any user data you can, log any data that may be useful.) – idz Jun 27 '19 at 21:31
  • That's true, there could be a chance for that but I don't think that's now what the OP wants to do. – Sulthan Jun 27 '19 at 22:06
  • 1
    @Sulthan that's a good point. I'll update the answer with a bit more info. – idz Jun 27 '19 at 22:10
0

NSInternalInconsistencyException is an Objective-C exception which cannot be catched by Swift code. You can only catch this type of exception with Objective-C code so you will need to create an Objective-C wrapper to catch this from Swift code for example with following Objective-C method:

+ (NSException *)tryCatchWithBlock:(void (^)(void))block {
    @try {
        block();
    } @catch (NSException *exception) {
        return exception;
    } @catch (id exception) {
        return [NSException exceptionWithName:NSGenericException reason:nil userInfo:nil];
    }
    return nil;
}

This method is a part of my library called LSCategories: https://github.com/leszek-s/LSCategories with various useful categories/extensions so you can also easily integrate this library with CocoaPods into your Swift project and then you can catch NSInternalInconsistencyException by wrapping your swift code like that:

let objcException = NSException.lsTryCatch {
    // put your swift code here
}

So that is how you can catch this exception if you want to do it. But more importantly you should investigate why this exception occurs in your case. Perhaps you are calling your code on a background thread.

Leszek Szary
  • 8,558
  • 1
  • 49
  • 46
  • Could you point to the specific method declaration instead of a big utility repo? Or better, could you copy the method implementation here? Otherwise this doesn't work well as an answer and it's more a self-promotion. – Sulthan Jun 28 '19 at 19:26
  • As a side note, your `lsDateWithStringWithISO8601` won't work correctly if you don't set time zone and note that most checksum functions (crc32, adler) are available in zlib. – Sulthan Jun 28 '19 at 19:30
  • Thanks for note about ISO8601 and yes I know that those checksums are also available in zlib. – Leszek Szary Jun 28 '19 at 20:32
-1

I guess the crash occurs when you try to convert modifiedFont to Data.
modifiedFont.data(using: String.Encoding.unicode, allowLossyConversion: true)! Most likely you will get the same error if you move the data conversion line out of the try-catch scope. In order to avoid the crash, don't use force unwrap(!).

It is being caught if any error throws during initialization NSMutableAttributedString.

hasankose
  • 283
  • 3
  • 10