3

I have a class called User()

class User {
    var name: String?
    var email: String?
    var id: String?
    var identification_number: String?
    var phone_number: NSMutableArray?    
    var user_group: String?
    var date: NSDate?
}

I want to get all of the variables in the class and their respective values. I am trying to use Mirror in this case.

func updateProfile(user: User) {

    let mirror = Mirror(reflecting: user)

    for child in mirror.children {
        print("\(child.label!), \(child.value)")
    }

}

My question is, how can I convert child.value to any other datatype, say String ? I only got to find out that child.value belongs to the Protocol 'Any'

rmaddy
  • 314,917
  • 42
  • 532
  • 579
munfai
  • 83
  • 9
  • I am not understanding why `child.value is String` is always `false`. Even when the `child` does refer a `String` property (like `name`) which I have populated. – Luca Angeletti Jan 06 '16 at 16:49

2 Answers2

3

child.value has the Any type. Casting from Any to an optional poses some problems, fortunately Sandy Chapman gave a very nice solution in this post.

With his function, the code would look like this:

func castToOptional<T>(x: Any) -> T? {
    return Mirror(reflecting: x).descendant("Some") as? T
}

func updateProfile(user: User) {
    let mirror = Mirror(reflecting: user)
    for child in mirror.children {
        print("\(child.label!), \(child.value)")
        if let stringVal = castToOptional(child.value) as String? {
            print("Unwrapped a string: \(stringVal)")
        } else if let stringVal = child.value as? String {
            print("Found a non-optional string: \(stringVal)")
        }
    }
}

So if you're looking for strings, you need to look for both optional and non-optional ones. This applies to all types you need to check.

Community
  • 1
  • 1
Cristik
  • 30,989
  • 25
  • 91
  • 127
  • The solution looks perfectly valid. However in Playground the execution never enter the `IF` block. Of course I populated the instance `let user = User() user.name = "1"` Any clue? – Luca Angeletti Jan 06 '16 at 16:36
  • Maybe there are no strings in `child.value`? – Cristik Jan 06 '16 at 16:37
  • I assigned a proper `String` value to the `name` property. – Luca Angeletti Jan 06 '16 at 16:38
  • On the other hand this code `let any: Any? = "Hello world"; if let stringValue = any as? String { print(stringValue) } else { print("No string found") }` does work as expected and the output is "Hello world". – Luca Angeletti Jan 06 '16 at 16:40
  • 1
    @appzYourLife this drove me also crazy, so I did some digging on the internet, and found the solution to casting `Any` to an `Optional`, ironically or not, on SO :). I updated my answer with the finding. – Cristik Jan 06 '16 at 22:11
2

Create a protocol for extending Optional<Any> type to return it's non-optional-value:

private protocol AnyOptional {
    var objectValue: Any? { get }
}

extension Optional: AnyOptional {
    var objectValue: Any? {
        switch self {
        case .None:
            return nil
        case .Some(_):
            return self! as Any
        }
    }
}

Thereafter you can use AnyOptional protocol as a type, and cast Any? objects to AnyOptional, thereafter allowing us to make use of the .objectValue property of AnyOptional

class User {
    var name: String?
    var email: String?
    var id: String = "Default ID" // Lets try also with one non-optional
    var identification_number: String?
    var phone_number: NSMutableArray?
    var user_group: String?
    var date: NSDate?
}

var myUser = User()
myUser.name = "John"
myUser.phone_number = ["+44", "701 23 45 67"]

func updateProfile(user: User) {

    let mirror = Mirror(reflecting: user)

    for child in mirror.children {
        let value : Any = (child.value as? AnyOptional)?.objectValue ?? child.value

        switch(value) {
        case let obj as String: print("String item: User.\(child.label!) = " + obj)
        case let obj as NSMutableArray: print("NSMutableArray item: User.\(child.label!) = \(obj)")
        case let obj as NSDate: print("NSDate item: User.\(child.label!) = \(obj)")
        case _ : print("Non-initialized optional item: User.\(child.label!) = \(value)")
        }
    }
}

Which yields the following output

updateProfile(myUser)

/*
String item: User.name = John
Non-initialized optional item: User.email = nil
String item: User.id = Default ID
Non-initialized optional item: User.identification_number = nil
NSMutableArray item: User.phone_number = (
    "+44",
    "701 23 45 67"
)
Non-initialized optional item: User.user_group = nil
Non-initialized optional item: User.date = nil       */

The benefit of using this solution is that it will "unwrap" optional non-nil values of child.value (without the "Optional(...)" padding) as well as values of child.value that are not optional, without the need of separate "unwrapping" for the two cases. In the switch case above, you can handle whatever non-nil property of the User object that you need to work with, not just as String but any of the types in your User class. The obj property in the switch case will be of the non-optional type of each of the non-nil properties of your class. The default case corresponds to optionals with value nil (not assigned).

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • hey @dfri wonderful solution here! thanks! i was wondering, from your `switch(value)` here, how and where do I get the `obj`? and would it be possible to cast it to `AnyObject`? – munfai Jan 14 '16 at 03:43
  • @munfai Happy to help. The `... let obj as ...` in the switch cases assigns `value` to immutable `obj` as the type `... as Type`, _if possible_. Hence, within each case, if you enter it, `obj` will be an immutable of the type the case covers (downcast from `Any`). Regarding you 2nd question: you _could_ try to cast `Any` to `AnyObject`, but I suggest you stick to using one of them (or none, if preferable..). `AnyObject` can only hold only class types (hence cannot hold _native_ swift value types `String`, `Int` and so on), whereas `Any` can hold class as well as value types. – dfrib Jan 14 '16 at 11:39