8

Lets say I have this class:

class Node {
    var value: String
    var children: [Node]?
}

If I have the name of one of its properties (for example "children") how can I get its type? (In this case [Node]?)

I imagine having a global function like below will solve my needs:

func typeOfPropertyWithName(name: String, ofClass: AnyClass) -> AnyClass? {
    //???
}

// Example usage:
var arrayOfNodesClass = typeOfPropertyWithName("children", Node.self)
Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41
nacho4d
  • 43,720
  • 45
  • 157
  • 240

5 Answers5

8

Swift 2 (Note: Reflection changed):

import Foundation

enum PropertyTypes:String
{
    case OptionalInt = "Optional<Int>"
    case Int = "Int"
    case OptionalString = "Optional<String>"
    case String = "String"
    //...
}

extension NSObject{
    //returns the property type
    func getTypeOfProperty(name:String)->String?
    {
        let type: Mirror = Mirror(reflecting:self)

        for child in type.children {
            if child.label! == name
            {
                return String(child.value.dynamicType)
            }
        }
        return nil
    }

    //Property Type Comparison
    func propertyIsOfType(propertyName:String, type:PropertyTypes)->Bool
    {
        if getTypeOfProperty(propertyName) == type.rawValue
        {
            return true
        }

        return false
    }
}

custom class:

class Person : NSObject {
    var id:Int?
    var name : String?
    var email : String?
    var password : String?
    var child:Person?
}

get the type of the "child" property:

let person = Person()
let type = person.getTypeOfProperty("child")
print(type!) //-> Optional<Person>

property type checking:

print( person.propertyIsOfType("email", type: PropertyTypes.OptionalInt) ) //--> false
print( person.propertyIsOfType("email", type: PropertyTypes.OptionalString) //--> true

or

if person.propertyIsOfType("email", type: PropertyTypes.OptionalString)
{
    //true -> do something
}
else
{
    //false -> do something
}
Peter Kreinz
  • 7,979
  • 1
  • 64
  • 49
3

Reflection is achieved in Swift using the global reflect() function. When passing an instance of some type to reflect() it returns a MirrorType, which has a range of properties allowing you to analyze your instance:

var value: Any { get }  
var valueType: Any.Type { get }
var objectIdentifier: ObjectIdentifier? { get }  
var count: Int { get }  
var summary: String { get }  
var quickLookObject: QuickLookObject? { get }  
var disposition: MirrorDisposition { get }    
subscript(i: Int) -> (String, MirrorType) { get }
Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41
  • 1
    I haven't heard about `reflect()` or `MirrorType` before. Thanks for teaching me something (voted.) Are those documented anywhere else other than the SwiftDoc.org site? I don't see it in either of the Swift iBooks or the Standard Library reference. The SwiftDoc site is an alphabetical reference, and not great for learning. (You have to know what you're looking for in order to use it.) – Duncan C Apr 05 '15 at 15:37
  • Reflection in Swift doesn't seem to be documented officially by Apple. As stated on the SwiftDoc.org website it is "Auto-generated documentation for Swift.", which is why it's only ordered alphabetically. I find it very useful though, as you can see most of what Swift itself has to offer. – Marcus Rossel Apr 05 '15 at 15:41
  • How did you learn about `reflect()` and `MirrorType`? Exploring SwiftDoc.org? – Duncan C Apr 05 '15 at 15:44
  • More or less. I do sometimes browse SwiftDoc, and did come upon `MirrorType` a while ago. I didn't understand it though until I read some articles on reflection in Swift. Initially I simply wanted to get the name of a variable as a String and ended up stumbling upon this StackOverflow-Question: http://stackoverflow.com/questions/24006165/how-do-i-print-the-type-or-class-of-a-variable-in-swift – Marcus Rossel Apr 05 '15 at 15:47
1

This seems to work:

func getTypeOfVariableWithName(name: String, inInstance instance: Any) -> String? {
    let mirror = reflect(instance)
    var variableCollection = [String: MirrorType]()

    for item in 0..<mirror.count {
        variableCollection[mirror[item].0] = mirror[item].1
    }

    if let type = variableCollection[name] {
       let longName = _stdlib_getDemangledTypeName(type.value)
       let shortName = split(longName, { $0 == "."}).last
       
       return shortName ?? longName
    }

    return nil
}

Here's some example code on SwiftStub.


Edit:

The result for optional values is only "Optional".
The result for arrays is only "Array".
The result for dictionaries is only "Dictionary".

I'm not sure if it is possible to extract what kind of optional/array/dictionary it is. But I guess this would also be the case for custom data structures using generics.

Community
  • 1
  • 1
Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41
1

Building on @PeterKreinz answer I needed to be able to check types of inherited properties as well so added a little to his above code:

extension NSObject {

    // Returns the property type
    func getTypeOfProperty (name: String) -> String? {

        var type: Mirror = Mirror(reflecting: self)

        for child in type.children {
            if child.label! == name {
                return String(child.value.dynamicType)
            }
        }
        while let parent = type.superclassMirror() {
            for child in parent.children {
                if child.label! == name {
                    return String(child.value.dynamicType)
                }
            }
            type = parent
        }
        return nil
    }

}

Hope this may help someone.

Swift 3 update:

// Extends NSObject to add a function which returns property type
extension NSObject {

    // Returns the property type
    func getTypeOfProperty (_ name: String) -> String? {

        var type: Mirror = Mirror(reflecting: self)

        for child in type.children {
            if child.label! == name {
                return String(describing: type(of: child.value))
            }
        }
        while let parent = type.superclassMirror {
            for child in parent.children {
                if child.label! == name {
                    return String(describing: type(of: child.value))
                }
            }
            type = parent
        }
        return nil
    }
}
AtheistP3ace
  • 9,611
  • 12
  • 43
  • 43
0

The solution provided by @peter-kreinz using Swift's class Mirror works beautifully when you have an instance of a class, and want to know the types of the properties. However if you want to inspect the properties of a class without having an instance of it you might be interested in my solution.

I have a solution that finds the name and type of a property given any class that inherits from NSObject.

I wrote a lengthy explanation on StackOverflow here, and my project is available here on Github,

In short you can do something like this (but really check out the code Github):

public class func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? {
    var count = UInt32()
    guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
    var types: Dictionary<String, Any> = [:]
    for i in 0..<Int(count) {
        guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
        let type = getTypeOf(property: property)
        types[name] = type
    }
    free(properties)
    return types
}
Community
  • 1
  • 1
Sajjon
  • 8,938
  • 5
  • 60
  • 94