Exception While Dynamically Cloning Object with an Enum setValue

I am attempting to dynamically clone a class object in Swift using reflection and the setValue function. This class contains an enum property that is backed with an Int, which is causing this dynamic reflection to crash:

@objc enum Status : Int {
    case green
    case yellow
    case red
}

@objc class State : NSObject {
    @objc var status : Status
    
    init(_ status: Status) {
        self.status = status
    }
}

func testReflectiveClone() {
    let state1 = State(.green)
    let state2 = State(.yellow)
    
    let state1Mirror = Mirror(reflecting: state1)
    
    for property in state1Mirror.children.enumerated() {
        let label = property.element.label
        state2.setValue(property.element.value, forKey: label!) //crashes here
    }
}

This test function is throwing the following error in XCode:

-[__SwiftValue longLongValue]: unrecognized selector sent to instance 0x600001e5a340 (NSInvalidArgumentException)

Is it even possible to dynamically set enum values? What modification would I need to make to get this to work?

Answered by DTS Engineer in 714541022

The problem here is that you’re mixing Swift reflection with Objective-C key-value-coding. property.element.value returns a Swift Any that boxes up the native Status enum value. This is where the __SwiftValue type comes from. You then pass that to a KVC routine, setValue(_:forKey:), which is expecting the passed-in value to follow Objective-C norms, that is, to support the -longLongValue method.

You could make this work by going all in on KVC:

let label = property.element.label!
let v = state1.value(forKey: label)
state2.setValue(v, forKey: label)

But, sheesh, you’re playing with fire here. If you continue down this path you will run into other problems.

Is this something you plan to ship to a wide array of users? Or something that you’re only going to use yourself?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

The problem here is that you’re mixing Swift reflection with Objective-C key-value-coding. property.element.value returns a Swift Any that boxes up the native Status enum value. This is where the __SwiftValue type comes from. You then pass that to a KVC routine, setValue(_:forKey:), which is expecting the passed-in value to follow Objective-C norms, that is, to support the -longLongValue method.

You could make this work by going all in on KVC:

let label = property.element.label!
let v = state1.value(forKey: label)
state2.setValue(v, forKey: label)

But, sheesh, you’re playing with fire here. If you continue down this path you will run into other problems.

Is this something you plan to ship to a wide array of users? Or something that you’re only going to use yourself?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Exception While Dynamically Cloning Object with an Enum setValue
 
 
Q