Swift Enums in Objective C

Using a Swift Integer enum in Objective C (the only Swift enum type that can be used in Objective C) is problematic. A public Objective C property for the enum can't be created directly. Consider:

// In a Swift file
enum StringStyle: Int {
    case none
    case bold
    case italic
}

// In an Objective C header file
// The Swift header cannot be imported in an Objective C header file...
#import "Project-Swift.h" // Can't do this

@interface MyClass: NSObject {
    // ...so a property of the enum type cannot be declared
    // because the enum type is not visible here without the Swift.h import
    @property (nonatomic, assign) StringStyle style; // Can't do this
}

One work around is to declare an NSInteger in the Objective C header file as a stand in for the enum type instead:

// In an Objective C header file
@property (nonatomic, assign) NSInteger style;

Unfortunately, any function that moves the NSInteger/enum between Swift and Objective C code will require conversion between the Objective C NSInteger and the Swift enum type.

This is a pain.

However, the effect of a public Swift enum property in Objective C code can still be achieved. The conversion between an NSInteger and the Swift enum can be centralized, thereby simplifying all bridging code between Objective C and Swift. This solution works through a Swift extension on the Objective C class that provides the enum property.

Ah, but a new property can't be declared through an extension! This is true, but easily worked around.

A computed property can be declared that creates an associated object as the backing store for the property. It will appear to be a public property of the Objective C class and of the enum type. Client code will not need to perform conversions between NSInteger and Swift enum representations since client code will only see a Swift enum type, as it should. The enum values can be used directly within and passed between both Objective C and Swift code. Here is an example of the computed property in an extension of the Objective C class:

extension MyClass {
    // Create a key to reference the associated object
    private struct Key {
        static var style = "StringStyle"
    }
    
    // This will appear as a public property on instances of the Objective C class
    @objc var style: StringStyle {
    	get {
            guard
                let value = objc_getAssociatedObject(self, &Key.style) as? NSNumber,
                let style = StringStyle(rawValue: value.intValue)
            else {
                // Default value when no value has been set for the property before
                return .none
            }
            
            return style
        }
        
        set {
            objc_setAssociatedObject(self, &Key.style, NSNumber(value: newValue.rawValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

As shown below, the computed property can be used directly from both Objective C and Swift code.

// In an Objective C implementation file
MyClass *myClass = [[MyClass alloc] init];
myClass.style = StringStyleBold;
NSLog(@"Style: %ld", myClass.style);

// In a Swift file
let myClass = MyClass()
myClass.style = .bold
print("Style: \(myClass.style)")

I've found this technique especially helpful in a mixed Swift/Objective C code base where new functionality is being added in Swift, but Objective C code that can't be converted yet needs to take advantage of new functionality too. Eventually, when all Objective C code is converted, it's a simple task to replace the computed property with a true property since no code outside of the property needs to be changed.