Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting the value of a property using it's string name in pure Swift using reflection

I want to use Swift (not Objective-C runtime) Reflection to create a method like this:

func valueFor(property:String, of object:Any) -> Any? {
    ...
}

To some extent, I can do this using:

func valueFor(property:String, of object:Any) -> Any? {
    let mirror = Mirror(reflecting: object)
    return mirror.descendant(property)
}

With

class TestMe {
    var x:Int!
}

let t = TestMe()
t.x = 100
let result = valueFor(property: "x", of: t)
print("\(result); \(result!)")

This prints out what I'd expect:

Optional(100); 100

When I do:

let t2 = TestMe()    
let result2 = valueFor(property: "x", of: t2)
print("\(result2)")

The output is:

Optional(nil)

This might seem reasonable, except that if I do:

var x:Int!
print("\(x)")

This prints out:

nil

and not Optional(nil). The bottom line is that I'm having difficulty programmatically determining that the value of t2.x is nil using my valueFor method.

If I continue the above code with:

if result2 == Optional(nil)! {
    print("Was nil1")
}

if result2 == nil {
    print("Was nil2")
}

Neither of these print statements output anything.

When I put a breakpoint into Xcode and look at the value of result2 with the debugger, it shows:

▿ Optional<Any>
  - some : nil

So, my question is: How can I determine if the original member variable was nil using the result from valueFor?

Additional1: If I do:

switch result2 {
case .some(let x):
    // HERE
    break
default:
    break
}

and put a breakpoint at HERE, the value of x turns out to be nil. But, even if I assign it to an Any?, comparing it to nil is not true.

Additional2: If I do:

switch result2 {
case .some(let x):
    let z:Any? = x
    print("\(z)")
    if z == nil {
        print("Was nil3")
    }
    break
default:
    break
}

This prints out (only):

Optional(nil)

I find this especially odd. result2 prints out exactly the same thing!

like image 860
Chris Prince Avatar asked May 04 '17 23:05

Chris Prince


1 Answers

This is a bit of a hack, but I think it's going to solve the problem for me. I'm still looking for better solutions:

func isNilDescendant(_ any: Any?) -> Bool {
    return String(describing: any) == "Optional(nil)"
}

func valueFor(property:String, of object:Any) -> Any? {
    let mirror = Mirror(reflecting: object)
    if let child = mirror.descendant(property), !isNilDescendant(child) {
        return child
    }
    else {
        return nil
    }
}
like image 77
Chris Prince Avatar answered Nov 03 '22 15:11

Chris Prince