List of class's properties in swift
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.
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()
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
}