2

Swift 3 changes how NSCoder works.

As other SO questions mention, to decode value types like Int or Bool, you must use a specific function. For instance, decodeInteger is used to decode Int values like so:

let value = decodeInteger(forKey key: TestKey)

But what if the value returned from decodeInteger is a String or Bool or something other than an Int?

Or what if TestKey actually maps to nothing because it contains the wrong key data?

How do you catch these errors gracefully?

Crashalot
  • 31,452
  • 56
  • 235
  • 393

1 Answers1

5

Using decodeInteger on a non-integer key would raise an exception. Sadly, it's an NSException which Swift cannot handle directly (see references below).

You need to first write a wrapper to handle ObjC exceptions in ObjC and bridge it over to Swift (inspired by this answer):

/// -------------------------------------------
/// ObjC.h
/// -------------------------------------------
#import <Foundation/Foundation.h>

@interface ObjC : NSObject

+ (BOOL)catchException:(void(^)())tryBlock error:(__autoreleasing NSError **)error;

@end

/// -------------------------------------------
/// ObjC.m
/// -------------------------------------------
#import "ObjC.h"

@implementation ObjC

+ (BOOL)catchException:(void(^)())tryBlock error:(__autoreleasing NSError **)error {
    @try {
        tryBlock();
        return YES;
    }
    @catch (NSException *exception) {
        NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithDictionary:exception.userInfo];
        [userInfo setValue:exception.reason forKey:NSLocalizedDescriptionKey];
        [userInfo setValue:exception.name forKey:NSUnderlyingErrorKey];

        *error = [[NSError alloc] initWithDomain:exception.name
                                            code:0
                                        userInfo:userInfo];
        return NO;
    }
}

@end

Now you can catch the exception in Swift:

do {
    try ObjC.catchException {
        let age = aDecoder.decodeInteger(forKey: "firstName")
    }
} catch {
    print(error.localizedDescription)
}

References: Using ObjectiveC with Swift: Adopting Cocoa Design Patterns

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.

Community
  • 1
  • 1
Code Different
  • 73,850
  • 14
  • 125
  • 146
  • So is it incorrect to say this is much worse than Swift 2 which let you catch errors like this: `if let value = aDecoder.decodeObjectForKey(TestKey) as? Bool { test = value }` – Crashalot Sep 24 '16 at 03:47
  • I don't have Xcode 7 to test that, but if it did work on Swift 2, then yes, Swift 3 made it worse. And this won't be the only thing made worse by Swift 3. Pointer was a pain in Swift 2, a much bigger pain in Swift 3 – Code Different Sep 24 '16 at 03:50
  • Not related to your question. If you deal with C libraries that work with pointers, interfacing with them in Swift is a pain in the ass – Code Different Sep 24 '16 at 04:02