43

Note: there is a similar question posted for objective c over here, but I want to achieve it in swift.

I have a class declared in swift like this:

import UIKit

class EachDayCell : UITableViewCell
{

    @IBOutlet var dateDisplayLabel : UITextField
    @IBOutlet var nameDisplayLabel : UITextField

    @IBAction func goToPendingItems(sender : AnyObject) {
    }
    @IBAction func showDateSelectionPicker(sender : AnyObject) {
    }

    init(style: UITableViewCellStyle, reuseIdentifier: String!)
    {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    }
}

Now I want to get an array in swift enlisting: dateDisplayLabel, nameDisplayLabel.

How can I achieve this?

5 Answers 5

101

Using Mirror

Here's a pure Swift solution with some limitations:

protocol PropertyNames {
    func propertyNames() -> [String]
}

extension PropertyNames
{
    func propertyNames() -> [String] {
        return Mirror(reflecting: self).children.flatMap { $0.label }
    }
}

class Person : PropertyNames {
    var name = "Sansa Stark"
    var awesome = true
}

Person().propertyNames() // ["name", "awesome"]

Limitations:

  • Returns an empty array for Objective-C objects
  • Will not return computed properties, i.e.:

    var favoriteFood: String { return "Lemon Cake" }
    
  • If self is an instance of a class (vs., say, a struct), this doesn't report its superclass's properties, i.e.:

    class Person : PropertyNames {
        var name = "Bruce Wayne"
    }
    
    class Superhero : Person {
        var hasSuperpowers = true
    }
    
    Superhero().propertyNames() // ["hasSuperpowers"] — no "name"
    

    You could work around this using superclassMirror() depending on your desired behavior.

Using class_copyPropertyList

If you're using Objective-C objects you can use this approach:

var count = UInt32()
let classToInspect = NSURL.self
let properties : UnsafeMutablePointer <objc_property_t> = class_copyPropertyList(classToInspect, &count)
var propertyNames = [String]()
let intCount = Int(count)
for var i = 0; i < intCount; i++ {
    let property : objc_property_t = properties[i]
    guard let propertyName = NSString(UTF8String: property_getName(property)) as? String else {
        debugPrint("Couldn't unwrap property name for \(property)")
        break
    }

    propertyNames.append(propertyName)
}

free(properties)
print(propertyNames)

The output to the console if classToInspect is NSURL:

["pathComponents", "lastPathComponent", "pathExtension", "URLByDeletingLastPathComponent", "URLByDeletingPathExtension", "URLByStandardizingPath", "URLByResolvingSymlinksInPath", "dataRepresentation", "absoluteString", "relativeString", "baseURL", "absoluteURL", "scheme", "resourceSpecifier", "host", "port", "user", "password", "path", "fragment", "parameterString", "query", "relativePath", "hasDirectoryPath", "fileSystemRepresentation", "fileURL", "standardizedURL", "filePathURL"]

This won't work in a playground. Just replace NSURL with EachDayCell (or reuse the same logic as an extension) and it should work.

Sign up to request clarification or add additional context in comments.

9 Comments

@AaronBrager quick comment. You said that if self is a class, you won't get the superclass properties. However in your example, self is never a class because 1) propertyKeys isn't a class method so self will always refer to an instance and 2) you're calling it on Superhero().propertyKeys() not Superhero.propertyKeys()
@startupthekid I'm not talking about instance vs. class properties. I'm talking about when self is an instance of a class (vs., say, a struct)
this approach will not work with Int, Double and other datatypes of swift.
classToInspect Has to be an NSObject class while using class_copyPropertyList
is there a way to get property names and types without instantiating the class?
|
26

Here is another version.I think this is much simple and pure.

Swift 2.0

protocol Reflectable {
  func properties()->[String]
}

extension Reflectable
{
    func properties()->[String]{
        var s = [String]()
        for c in Mirror(reflecting: self).children
        {
            if let name = c.label{
                s.append(name)
            }
        }
        return s
    }
}


class Test:Reflectable
{
    var name99:String = ""
    var name3:String = ""
    var name2:String = ""
}

Test().properties()

Swift 1.2

class Reflect:NSObject {
    func properties()->[String]
    {
        let m = reflect(self)
        var s = [String]()
        for i in 0..<m.count
        {
            let (name,_)  = m[i]
            if name == "super"{continue}
            s.append(name)
        }
        return s
    }
}

class Test:Reflect
{
    var name99:String = ""
    var name3:String = ""
    var name2:String = ""
}

Test().properties()

1 Comment

For future on-lookers: This answer can easily also return the value of the property
6

I converted bolivia's code to Swift 4. This function takes in an NSObject and returns a dictionary of the object's keys and the type of that key.

Note that the types are kind of ugly. For primitive properties the engine returns a one letter identifier (like B for bool, i for int, etc) but for Obj-C types it returns things like @"NSString". Seeing as this is really just a debugging function for me I didn't mind. If you don't want to mess with the dictionary you can just uncomment the print line and get it dumped to the console. String(cString:cAttr) also contains a lot of useful info including if the property is mutable, it's reference style, and much more. For more info on this here's Apple's documentation.

func getKeysAndTypes(forObject:Any?) -> Dictionary<String,String> {
        var answer:Dictionary<String,String> = [:]
        var counts = UInt32()
        let properties = class_copyPropertyList(object_getClass(forObject), &counts)
        for i in 0..<counts {
            let property = properties?.advanced(by: Int(i)).pointee
            let cName = property_getName(property!)
            let name = String(cString: cName)
            
            let cAttr = property_getAttributes(property!)!
            let attr = String(cString:cAttr).components(separatedBy: ",")[0].replacingOccurrences(of: "T", with: "")
            answer[name] = attr
            //print("ID: \(property.unsafelyUnwrapped.debugDescription): Name \(name), Attr: \(attr)")
        }
        return answer
    }

1 Comment

Comparing the code: Why did you omit the free(properties) before returning?
1

You could also make an extension on NSObject like this:

extension NSObject {
    var properties: [Mirror.Child] {
        Mirror(reflecting: self).children.compactMap { $0 }
    }

    var propertyNames: [String] {
        properties.compactMap { $0.label }
    }

    var propertyValues: [Any] {
        properties.map { $0.value }
    }
}

Comments

0

Swift 3.1

let contorller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
var count: UInt32 = 0
guard let properties = class_copyPropertyList(object_getClass(contorller), &count) else {
    return
}
for index in 0...count {
    let property1 = property_getName(properties[Int(index)])
    let result1 = String(cString: property1!)
    print(result1)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.