Se7enDays
Se7enDays

Reputation: 2593

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?

Upvotes: 8

Views: 2934

Answers (1)

Ole Begemann
Ole Begemann

Reputation: 135558

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.

Upvotes: 10

Related Questions