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?

Community
  • 1
  • 1
Devarshi
  • 16,440
  • 13
  • 72
  • 125

5 Answers5

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.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
  • @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()` – barndog Sep 27 '15 at 08:00
  • @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) – Aaron Brager Sep 27 '15 at 13:56
  • this approach will not work with Int, Double and other datatypes of swift. – Ilker Baltaci Oct 12 '15 at 10:16
  • `classToInspect` Has to be an `NSObject` class while using `class_copyPropertyList` – GoodSp33d Nov 23 '15 at 08:24
  • Just for those who didn't know where to put the `superclassMirror()` it goes here `Mirror(reflecting: self).superclassMirror()?.superclassMirror()?.children.filter { $0.label != nil }.map { $0.label! }` – villy393 Nov 24 '15 at 14:55
  • `return Mirror(reflecting: self).children.flatMap { $0.label}` – aryaxt Jun 16 '16 at 14:43
  • 1
    is there a way to get property names and types without instantiating the class? – KSC Sep 15 '16 at 05:55
  • @KSC Not with the Swift mirror approach; the mirror of an uninitialized type (e.g. `Person.Type`) has zero children. – Aaron Brager Sep 16 '16 at 14:57
  • Doesn't compile for Swift 3 :( – TruMan1 Oct 28 '16 at 22:45
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()
John R Perry
  • 3,916
  • 2
  • 38
  • 62
Kiny
  • 349
  • 3
  • 5
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
    }
C0D3
  • 6,440
  • 8
  • 43
  • 67
Allison
  • 2,213
  • 4
  • 32
  • 56
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 }
    }
}
Cameron E
  • 1,839
  • 1
  • 19
  • 20
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)
}
Ramis
  • 13,985
  • 7
  • 81
  • 100