Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 847
Se7enDays Avatar asked Mar 16 '16 17:03

Se7enDays


1 Answers

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.

like image 150
Ole Begemann Avatar answered Sep 22 '22 05:09

Ole Begemann