如何在嵌套结构中获取所有 属性 名称

How to get all property names in nested Structs

假设我有以下结构:

struct Location: Codable, Loopable {
    var altitude: Double?
    var coordinate: Coordinate?
    struct Coordinate: Codable, Loopable {
        var latitude: Double?
        var longitude: Double?
    }
    var course: Double?
    var courseAccuracy: Double?
    var floor: Floor?
    struct Floor: Codable, Loopable {
        var level: Int?
    }
    var horizontalAccuracy: Double?
    var speed: Double?
    var speedAccuracy: Double?
    var timestamp: Timestamp?
    struct Timestamp: Codable, Loopable {
        var year: Int?
        var month: Int?
        var day: Int?
        var hour: Int?
        var minute: Int?
        var second: Int?
    }
    var verticalAccuracy: Double?
    var deviceName: String?
    var validPosition: Bool?
}

现在我想为结构实现两个方法。一个应该 return 所有 属性 名称中的所有 parents,另一个应该 return 这些属性的所有值。

第一个方法的结果应如下所示(我们将其命名为 allProperties()):

["altitude", "coordinate.latitude", "coordinate.longitude", "course", "courseAccuracy", "floor.level", "horzontalAccuracy", "speed", "speedAccuracy", "timeststamp.year", "timestamp.month", "timestamp.day", "timeststamp.hour", "timestamp.minute", "timestamp.second", "verticalAccuracy", "deviceName", "validPosition"]

第二种方法(我们将其命名为 allValues())的结果应如下所示:

[500.0, 48.000000, 10.00000, 120.0, 5.0, 4, 5.0, 3.0, 1.0, 2021, 07, 25, 22, 43, 50, 10.0, "iPhone", true]

如您所见,我的示例结构与其他结构嵌套在一起。在我的真实项目中,结构有两个以上的嵌套“级别”。 属性 类型也不是唯一的。在最底层,它们都是基本数据类型(Double、Bool、String、Int)。

我试图从这个线程修改用递归方法解析嵌套结构的解决方案,这似乎是一个优雅的解决方案:How to loop over struct properties in Swift?

现在我的问题:

["some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some", "some"]

到目前为止,这是我的协议代码:

protocol Loopable {
    func allProperties(limit: Int) -> [String]
    func allValues(limit: Int) -> [Any]
}

extension Loopable {
    func allProperties(limit: Int = Int.max) -> [String] {
        return props(obj: self, count: 0, limit: limit)
    }
    
    func allValues(limit: Int = Int.max) -> [Any] {
        return values(obj: self, count: 0, limit: limit)
    }

    private func props(obj: Any, count: Int, limit: Int) -> [String] {
        let mirror = Mirror(reflecting: obj)
        var result: [String] = []
        for (prop, val) in mirror.children {
            guard let prop = prop else { continue }
            if limit == count {
                result.append(prop)
            } else {
                let subResult = props(obj: val, count: count + 1, limit: limit)
                subResult.count == 0 ? result.append(prop) : result.append(contentsOf: subResult)
            }
        }
        return result
    }
    
    private func values(obj: Any, count: Int, limit: Int) -> [Any] {
        let mirror = Mirror(reflecting: obj)
        var result: [Any] = []
        for (_, val) in mirror.children {
            //guard let val = val else { continue }   // This line does not compile
            if limit == count {
                result.append(val)
            } else {
                let subResult = values(obj: val, count: count + 1, limit: limit)
                subResult.count == 0 ? result.append(val) : result.append(contentsOf: subResult)
            }
        }
        return result
    }
}

我在这里做错了什么?为什么 属性 标签总是“一些”?

所以 “some” 的问题源于您的值是可选的,可选值是一个枚举,因此您的反思正在接受它。这就是为什么你不能在 private func values(obj: Any, count: Int, limit: Int) -> [Any] 中执行保护,因为你没有展开到具体类型。

由于您的所有子类型都确认为 Loopable,我们可以重构您的代码以检查类型是否符合 Loopable。检查某物是否为空比检查其计数是否为零更有效,因此您应该尽可能使用 属性。

我们现在使用 prop 值作为前缀,以便我们可以获得您要查找的名称,但是,因为您的值是可选的,所以它们被包装在一个枚举中,因此您必须从字符串中删除 .someval 的类型是 Any,这意味着如果它是可选的,我们就不能在不知道它的具体类型是什么的情况下解包它,因此我们需要执行上面的舞蹈来删除 .some从前缀。


import Foundation


protocol Loopable {
    func allProperties() -> [String]
}

extension Loopable {
    func allProperties() -> [String] {
        return props(obj: self)
    }
    
    private func props(obj: Any, prefix: String = "") -> [String] {
        let mirror = Mirror(reflecting: obj)
        var result: [String] = []
        for (prop, val) in mirror.children {
            guard var prop = prop else { continue }
   
            // handle the prefix
            if !prefix.isEmpty {
                prop = prefix + prop
                prop = prop.replacingOccurrences(of: ".some", with: "")
            }
   
            if let _ = val as? Loopable {
                let subResult = props(obj: val, prefix: "\(prop).")
                subResult.isEmpty ? result.append(prop) : result.append(contentsOf: subResult)
            } else {
                result.append(prop)
            }
        }
        return result
    }
}
                    
           

这是一个简单的结构,我们可以用它来测试上面的代码。

struct User: Loopable {
    let name: String
    let age: Age?
    
    struct Age: Loopable {
        let value: Int?
        let day: Day?
        
        struct Day: Loopable {
            let weekday: Int?
        }
    }
}

let user = User(name: "mike", age: .init(value: 20, day: .init(weekday: 5)))

print(user.allProperties())

这将打印出以下内容

["name", "age.value", "age.day.weekday"]