Ned
Ned

Reputation: 6270

Enum defined with NS_ENUM's rawValue initializer doesn't fail

I have an Objective-C file with an enum defined like:

typedef NS_ENUM(NSInteger, State) {
  State_ACTIVE = 0,
  State_PENDING = 1,
  State_CANCELED = 2
};

In my swift code, if I do let state = State(rawValue: 100), usually this should return nil, since it's a failable initializer. However, when the enum is declared as such (with NS_ENUM), initialization succeeds, and there's no indication that that is an invalid enum value. Is this a bug in Xcode, or working as intended?

Upvotes: 3

Views: 4317

Answers (2)

iosuser2332
iosuser2332

Reputation: 41

It is possible to define an extra initialiser in an extension of the enumeration to implement the (Swift-like) behaviour to suit your needs:

extension State
{
    static var allRawCases: [Int]
    {
        var array = [Int]()
        switch State.ACTIVE
        {
        case .ACTIVE:
           array.append(State.ACTIVE.rawValue)
           fallthrough
        case .CANCELED:
           array.append(State.CANCELED.rawValue)
           fallthrough
        case .PENDING:
           array.append(State.PENDING.rawValue)
        }
        return array
    }
}

extension State
{
    init?(raw: Int)
    {
       guard State.allRawCases.contains(raw) else
       {
          return nil
       }
       self = State(rawValue: raw)!
    }
}

Pros:

  1. If you add / remove enum case in Objective-c code the compiler will display an error and you will adopt the code.

  2. New constructor behaves as default constructor of a native swift enumeration (without associated values).

  3. In next piece of code it's better to have a nil variable instead of an "invalid" value. This value leads to a crash in swift 3 + Xcode 10 project.

    // intValue differs from raw enumeration values e.g 100 
    guard let state = State(rawValue: intValue) else
    {
        return
    }
    
    switch state
    {
    case .ACTIVE:
        // do active staff            
    case .CANCELED:
        // do canceled staff
    case .PENDING:
        // do pending staff
    }
    

Cons:

  1. The allRawCases property grows proportionally to the number of elements in the Objective-c enumeration.
  2. Debugging of a large enumeration hardly will make you happy.

Upvotes: 0

JAL
JAL

Reputation: 42449

This is intended behavior. For any NS_ENUMs bridged to Swift the constructor will never return nil.

Try it with some other enums in the iOS SDK bridged to Swift with unexpected values. They will all return non-nil, even for a rawValue that is not defined by the enum:

UITableViewCellStyle(rawValue: 7) // "Optional(__C.UITableViewCellStyle)"
UITableViewCellAccessoryType(rawValue: 9999) // "Optional(__C.UITableViewCellAccessoryType)"

or, with unsafeBitCast:

unsafeBitCast(42, UITableViewCellEditingStyle.self) // "Optional(__C.UITableViewCellStyle)"

Martin R pointed out that this is documented in the Xcode 6.3 release notes:

Imported NS_ENUM types with undocumented values, such as UIViewAnimationCurve, can now be converted from their raw integer values using the init(rawValue:) initializer without being reset to nil. Code that used unsafeBitCast as a workaround for this issue can be written to use the raw value initializer. For example:

let animationCurve =  
     unsafeBitCast(userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue,
     UIViewAnimationCurve.self)

can now be written instead as:

let animationCurve = UIViewAnimationCurve(rawValue:  
    userInfo[UIKeyboardAnimationCurveUserInfoKey].integerValue)!

Upvotes: 1

Related Questions