Search code examples
swiftreflectionmirror

How to extract optional typed values from Mirror children in Swift?


I have several swift classes that look similar like the following

public class Book {
  var title: String?
  var date: NSDate?
}

As there are several different classes where I need to access the properties, I am using reflection to run through the properties of the class:

let myBook = Book()
myBook.title = "Hello"
myBook.date = NSDate()

let mirror = Mirror(reflecting: myBook)
var propsArr = [(key: String?, value: Any)]()
let mirrorChildrenCollection = AnyRandomAccessCollection(mirror.children)!
if mirrorChildrenCollection.count > 0 {
  propsArr += mirrorChildrenCollection
}

//iterate through properties
for case let (label?, value) in propsArr {
  print (label, value)

  if let val = value as? NSDate {
    var extractedDate = val
    print(extractedDate)
  }
  else if let val = value as? String {
    var extractedTitle = val
    print (extractedTitle)
  }
}

But I have a problem that the Child objects are not extracted as they are of Type Any and internally optional classes and thus do not fall into my cases. If I change title from String? to String, they do work, but I need to use optional types.

What can I change in the above implementation to leave the datatype as String? and Date? and still extract the values from the Mirror?


Solution

  • It seems this isn't possible in Swift 2.x.

    Since the properties are optionals, you would have to cast to NSDate? and String?, respectively, like this:

    if let val = value as? NSDate? {
        // val is Optional<NSDate>
    }
    

    Unfortunately, the compiler doesn't allow this (I’m not sure why): // error: cannot downcast from 'protocol<>' to a more optional type 'NSDate?'.

    This answer from bubuxu provides a clever workaround that would work if you had a Mirror for each property. The mirror's displayStyle property tells you if it is an optional, and you can then extract the wrapped value manually. So this would work:

    let child = Mirror(reflecting: myBook.date)
    child.displayStyle
    if child.displayStyle == .Optional {
        if child.children.count == 0 {
            // .None
        } else {
            // .Some
            let (_, some) = child.children.first!
            if let val = some as? NSDate {
                print(val)
            }
        }
    }
    

    But this depends on having a Mirror for each property, and it seems you can’t traverse a Mirror's children to retrieve Mirrors for them.