Swift中类的属性列表

43

注意:有一个关于 Objective-C 的类似问题发布在这里,但我想在 Swift 中实现它。

我在 Swift 中声明了一个类,如下所示:

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)
    }
}

现在我想在Swift中获取一个数组,列出:dateDisplayLabel、nameDisplayLabel。

我该如何实现?

5个回答

101

使用 Mirror

以下是一种纯 Swift 解决方案,但有一些限制:

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"]

限制:

  • 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.

使用 class_copyPropertyList

如果你正在使用 Objective-C 对象,你可以使用以下方法:

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)

如果classToInspectNSURL,则控制台的输出为:

["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"]

这在playground中不起作用。只需将NSURL替换为EachDayCell(或重用相同的逻辑作为扩展)即可正常工作。

@AaronBrager 快速评论。你说如果 self 是一个类,你将无法获得超类的属性。然而在你的例子中,self 从未是一个类,因为1)propertyKeys 不是一个类方法,所以 self 总是指向一个实例;2)你在 Superhero().propertyKeys() 上调用它,而不是 Superhero.propertyKeys() - barndog
@startupthekid 我不是在谈论实例属性和类属性的区别。我想说的是,当self是类的实例时(相对于结构体等其他情况)。 - Aaron Brager
这种方法无法处理Swift中的Int、Double和其他数据类型。 - Ilker Baltaci
1
有没有一种方法可以在不实例化类的情况下获取属性名称和类型? - KSC
无法在Swift 3中编译 :( - TruMan1
显示剩余4条评论

26

这里有另一个版本。我认为这个更加简单和纯粹。

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()

对于未来的观察者:这个答案也可以轻松地返回属性的值。 - Sentry.co

6
我将玻利维亚的代码转换为Swift 4。这个函数接收一个NSObject对象并返回一个包含该对象键和该键的类型的字典。

请注意,类型有点丑陋。对于基本属性,引擎返回一个字母标识符(比如B代表布尔类型,i代表整型等),但对于Obj-C类型,它返回类似@"NSString"的东西。由于这只是一个调试函数,我不介意。如果你不想处理字典,可以取消注释print行并将其转储到控制台。String(cString:cAttr)也包含很多有用的信息,包括属性是否可变、它的引用风格等等。更多信息请参阅苹果的文档。

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
    }

比较代码:为什么你在返回之前省略了 free(properties) - ctietze

1
你可以像这样在NSObject上创建一个扩展:
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 }
    }
}

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)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接