如何在嵌套结构中获取所有 属性 名称
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”的数组。
- 但是 属性 值的方法 return 是正确的数组
["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
值作为前缀,以便我们可以获得您要查找的名称,但是,因为您的值是可选的,所以它们被包装在一个枚举中,因此您必须从字符串中删除 .some
。 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"]
假设我有以下结构:
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”的数组。
- 但是 属性 值的方法 return 是正确的数组
["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
值作为前缀,以便我们可以获得您要查找的名称,但是,因为您的值是可选的,所以它们被包装在一个枚举中,因此您必须从字符串中删除 .some
。 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"]