【问题标题】:How to get all property names in nested Structs如何获取嵌套结构中的所有属性名称
【发布时间】:2021-10-01 23:10:41
【问题描述】:

假设我有以下结构:

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

现在我想为结构实现两种方法。一个应该返回名称中包含所有父级的所有属性名称,另一个应该返回这些属性的所有值。

第一个方法的结果应该如下所示(我们将其命名为 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", "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
    }
}

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

【问题讨论】:

    标签: swift struct properties


    【解决方案1】:

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

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

    我们现在使用 prop 值作为前缀,以便我们可以获取您正在寻找的名称,但是,因为您的值是可选的,它们被包装在一个枚举中,因此您必须去掉 @987654328 @ 来自字符串。 val 的类型是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"]

    【讨论】:

    • 非常感谢您的解决方案和解释。我想我必须阅读更多关于这种可选值类型的文档才能更好地理解它们为什么是枚举。
    • 为什么您的解决方案的输出遵循结构中属性的顺序(从上到下)?在上面提到的线程(stackoverflow.com/questions/27292255/…)中,输出的顺序是随机的。我只是想了解您解决方案的这一好功能?
    • 这是 Apple 关于可选参数的文档developer.apple.com/documentation/swift/optional 在您链接到的答案中输出是随机的,因为它使用字典,字典不保证顺序。在我的回答中(基于您的代码),我们使用的是数组并且数组是有序的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多