February 10, 2018

There are times when an ObjC method is not bridged to Swift in a Swifty way. One example is a method accepting an NSError** that returns an NSInteger (not a BOOL or reference type). Cocoa conventions require methods accepting an NSError** to have a non-void return type to indicate whether or not an error occurred. Methods returning a BOOL or reference type convert nicely to Swift. However, methods returning an NSInteger, for example, need some additional help.

Legacy ObjC Database API

Let's assume we have a legacy ObjC database API that doesn't return a BOOL or reference type. Instead the method returns an NSInteger, where NSNotFound is used to indicate an error.

` @interface HRLDatabase : NSObject

  • (BOOL)beginTransaction:(NSError Nullable Nullable)error;
  • (BOOL)commitTransaction:(NSError Nullable Nullable)error;

// If NSNotFound, then check the error; otherwise the return value represents the number of updated rows. - (NSInteger)executeUpdate:(nonnull NSString *)sql error:(NSError Nullable Nullable)error;


Here's the generated Swift API. 

open class HRLDatabase: NSObject {
open func beginTransaction() throws

open func commitTransaction() throws

open func executeUpdate(_ sql: String, error: NSErrorPointer) -> Int }

The "begin transaction" and "commit transaction" methods look great. However, the "execute update", not so much. 

## Refining ObjC Methods

We can [refine]( the "execute update" method by adding the `NS_REFINED_FOR_SWIFT` macro to end of the declaration. Here's how that looks.

@interface HRLDatabase : NSObject

  • (NSInteger)executeUpdate:(nonnull NSString )sql error:(NSError Nullable Nullable)error NSREFINEDFORSWIFT;


The `NS_REFINED_FOR_SWIFT` macro tells the compiler to emit a method that can be used in Swift extension. The emitted method
begins with `__` (double underscore).

open class HRLDatabase: NSObject {
open func _executeUpdate( sql: String, error: NSErrorPointer) -> Int }

Here's the refined Swift interface in an extension:

extension HRLDatabase {
func executeUpdate( sql: String) throws { var error: NSError? let result = _executeUpdate(sql, error: &error) if result == NSNotFound { throw error! } } }

## Example Usage

let database = HRLDatabase()

do { try database.beginTransaction() try database.executeUpdate("select from sometable") try database.commitTransaction() } catch { // handle error/ rollback }