是否可以在 Swift 中迭代结构体的属性?
我需要在使用许多不同单元类型的视图控制器中注册单元重用标识符(单元被组织在不同的 nib 文件中)。所以我的想法是将所有重用标识符和相应的 nib 文件作为静态元组属性(reuseID,nibName)放在结构中。但是我如何迭代所有这些单元格以将单元格注册到 tableView 中?
我已经尝试过一些东西(请参阅下面的我的答案)。但有没有更简单的方法来做到这一点,例如不将每个属性放入数组中?
虽然是老问题,但随着 Swift 的发展,这个问题有了新的答案。我认为您的方法对于所描述的情况更好,但是最初的问题是如何迭代结构属性,所以这是我的答案(适用于类和结构)
您可以使用镜像结构参考。关键是,在对某个对象调用
reflect
后,您会得到它的“镜子”,虽然反射相当少,但仍然有用。
因此我们可以轻松声明以下协议,其中
key
是属性名称,value
是实际值:
protocol PropertyLoopable
{
func allProperties() throws -> [String: Any]
}
当然我们应该利用新的协议扩展来提供该协议的默认实现:
extension PropertyLoopable
{
func allProperties() throws -> [String: Any] {
var result: [String: Any] = [:]
let mirror = Mirror(reflecting: self)
guard let style = mirror.displayStyle where style == .Struct || style == .Class else {
//throw some error
throw NSError(domain: "hris.to", code: 777, userInfo: nil)
}
for (labelMaybe, valueMaybe) in mirror.children {
guard let label = labelMaybe else {
continue
}
result[label] = valueMaybe
}
return result
}
}
现在我们可以用这个方法循环遍历 any
class
或 struct
的属性。我们只需将课程标记为 PropertyLoopable
即可。
为了保持静态(如示例中所示),我还将添加一个单例:
struct ReuseID: PropertyLoopable {
static let instance: ReuseID = ReuseID()
}
无论是否使用单例,我们最终都可以像下面这样循环遍历属性:
do {
print(try ReuseID.instance.allProperties())
} catch _ {
}
这就是循环结构属性。快速享受;)
使用 hris.to 很棒的答案,我想提供一个 Swift 3 答案,该答案更切题并且不使用单例。
protocol Loopable {
func allProperties() throws -> [String: Any]
}
extension Loopable {
func allProperties() throws -> [String: Any] {
var result: [String: Any] = [:]
let mirror = Mirror(reflecting: self)
// Optional check to make sure we're iterating over a struct or class
guard let style = mirror.displayStyle, style == .struct || style == .class else {
throw NSError()
}
for (property, value) in mirror.children {
guard let property = property else {
continue
}
result[property] = value
}
return result
}
}
struct Person: Loopable {
var name: String
var age: Int
}
var bob = Person(name: "bob", age: 20)
print(try bob.allProperties())
// prints: ["name": "bob", "age": 20]
现在有一种更简单的方法可以做到这一点:
1:创建可编码协议扩展:
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
2:使你的结构/类符合 Encodable 协议
struct MyStruct: Encodable {...}
class MyClass: Encodable {...}
然后你可以随时获取代表你的结构/类实例的字典:
var a: MyStruct
var b: MyClass
print(a.dictionary)
print(b.dictionary)
然后你可以循环按键:
for (key, value) in a.dictionary { ... }
for (key, value) in b.dictionary { ... }
我基于@John R Perry 的解决方案创建了一个递归函数,该函数更深入地研究对象的属性。它还需要一个参数来限制它的深度(默认为
Int.max
)以帮助防止 stackoverflow:
protocol Loopable {
func allProperties(limit: Int) [String: Any]
}
extension Loopable {
func allProperties(limit: Int = Int.max) [String: Any] {
return props(obj: self, count: 0, limit: limit)
}
private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
let mirror = Mirror(reflecting: obj)
var result: [String: Any] = [:]
for (prop, val) in mirror.children {
guard let prop = prop else { continue }
if limit == count {
result[prop] = val
} else {
let subResult = props(obj: val, count: count + 1, limit: limit)
result[prop] = subResult.count == 0 ? val : subResult
}
}
return result
}
}
我取消了对对象是否是
class
或struct
的检查,因为参数不是class
或struct
是递归函数的基本情况,并且更容易处理它手动而不是有错误。
测试:
class C {
var w = 14
}
class B: Loopable {
var x = 12
var y = "BHello"
var z = C()
static func test() -> String {
return "Test"
}
}
class A: Loopable {
var a = 1
var c = 10.0
var d = "AHello"
var e = true
var f = B()
var g = [1,2,3,4]
var h: [String: Any] = ["A": 0, "B": "Dictionary"]
var i: Int?
}
print(A().allProperties())
打印:
["e": true, "g": [1, 2, 3, 4], "f": ["z": ["w": 14], "x": 12, "y": "BHello"], "h": ["A": 0, "B": "Dictionary"], "c": 10.0, "i": nil, "d": "AHello", "a": 1]
(字典是无序的,所以如果你得到不同的顺序,这就是原因)
struct IdentifiersModel : Codable {
let homeReuseID: String = "Home_Reuse_ID"
let offersReuseID: String = "Offers_Reuse_ID"
let testReuseID: String = "Test_Reuse_ID"
func toDic() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
}
从结构体创建一个新实例并调用 toDic() 函数
let identifiersModel = IdentifiersModel()
print(identifiersModel.toDic())
输出:
["offersReuseID": "Offers_Reuse_ID", "testReuseID": "Test_Reuse_ID", "homeReuseID": "Home_Reuse_ID"]
这里是使用 Swifts 元组功能迭代结构体属性(重用 UITableViewCells 的标识符和相应的 NIB 名称)的示例。如果您喜欢在 nib 文件中组织单元格并且有一个使用许多不同单元格类型的 UIViewController,这非常有用。
struct ReuseID {
static let prepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
static let threeTitledIconCell = "ThreeTitledIconCell"
static let usageCell = "UsageCell"
static let detailsCell = "DetailsCell"
static let phoneNumberCell = "PhoneNumberCell"
static let nibNamePrepaidRechargeCreditCell = "PrepaidRechargeCreditCell"
static let nibNameThreeTitledIconCell = "IconCellWith3Titles"
static let nibNameUsageCell = "ListElementRingViewCell"
static let nibNameDetailsCell = "ListElementStandardViewCell"
static let nibNamePhoneNumberCell = "PhoneNumberCell"
static let allValuesAndNibNames = [
(ReuseID.prepaidRechargeCreditCell, ReuseID.nibNamePrepaidRechargeCreditCell),
(ReuseID.threeTitledIconCell, ReuseID.nibNameThreeTitledIconCell),
(ReuseID.usageCell, ReuseID.nibNameUsageCell),
(ReuseID.detailsCell, ReuseID.nibNameDetailsCell),
(ReuseID.phoneNumberCell, ReuseID.nibNamePhoneNumberCell)]
}
使用该结构,可以轻松使用 for 循环注册所有细胞类型:
for (reuseID, nibName) in ReuseID.allValuesAndNibNames {
if let xibPath = NSBundle.mainBundle().pathForResource(nibName, ofType: "nib") {
let fileName = xibPath.lastPathComponent.stringByDeletingPathExtension
self.tableView.registerNib(UINib(nibName: fileName, bundle: nil), forCellReuseIdentifier: reuseID)
} else {
assertionFailure("Didn't find prepaidRechargeCreditCell 👎")
}
}
知道在 Swift 1.2 中你可以使用
reflect()
,从 Swift 2 开始你可以使用 Mirror
,这里是 hris.to 对 Swift 3 和 4 的回答的补充。
protocol Loopable {
var allProperties: [String: Any] { get }
}
extension Loopable {
var allProperties: [String: Any] {
var result = [String: Any]()
Mirror(reflecting: self).children.forEach { child in
if let property = child.label {
result[property] = child.value
}
}
return result
}
}
在任何结构或类上的使用:
extension NSString: Loopable {}
print("hello".allProperties)
// ["_core": Swift._StringCore(_baseAddress: Optional(0x00000001157ee000), _countAndFlags: 5, _owner: nil)]
我发现 RPatel99 的答案最适合我,但它没有处理另一个 Loopable 中存在 Loopable 数组的情况。
这是解决该问题的版本。
protocol Loopable {
func allProperties(limit: Int) -> [String: Any]
}
extension Loopable {
func allProperties(limit: Int = Int.max) -> [String: Any] {
return props(obj: self, count: 0, limit: limit)
}
private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
let mirror = Mirror(reflecting: obj)
var result: [String: Any] = [:]
for (property, value) in mirror.children {
var val = value
if let values = value as? [Loopable] {
var vals = [Any]()
for val in values {
vals.append(val.allProperties())
}
val = vals
}
guard let prop = property else { continue }
if limit == count {
result[prop] = val
} else {
let subResult = props(obj: val, count: count + 1, limit: limit)
result[prop] = subResult.count == 0 ? val : subResult
}
}
return result
}
}
class C {
var w = 14
}
class B: Loopable {
var x = 12
var y = "BHello"
var z = C()
static func test() -> String {
return "Test"
}
}
class A: Loopable {
var a = 1
var c = 10.0
var d = "AHello"
var e = B()
var f = [B(), B(), B()]
var g = [1,2,3,4]
var h: [String: Any] = ["A": 0, "B": B().allProperties()]
var i: Int?
}
print(A().allProperties())
我得到的结果是这样的 [“h”:[“A”:0,“B”:[“z”:[“w”:14],“y”:“BHello”,“x”:12]],“e”:[ “y”:“BHello”,“z”:[“w”:14],“x”:12],“f”:[[“y”:“BHello”,“z”:[“w”: 14],“x”:12],[“x”:12,“z”:[“w”:14],“y”:“BHello”],[“y”:“BHello”,“x” :12,“z”:[“w”:14]]],“i”:无,“g”:[1,2,3,4],“a”:1,“d”:“AHello” ,“c”:10.0]
我希望其他人会发现这很有用。 这个结果或许也可以通过这样的方式来实现
我建立在Donovan Smith的出色答案之上,我发现一个结构体有一堆可选和包装值,我最终得到的值是看起来像
["some": "Value of optional"]
的字典,而我真的只想将"Value of optional"
作为字符串(或 Int 或其他任何东西)。当这些值被发现时,这会解压它们,或者当它们没有被发现时,将它们原封不动地传递。
protocol Loopable {
func allProperties(limit: Int) -> [String: Any]
}
extension Loopable {
func allProperties(limit: Int = Int.max) -> [String: Any] {
return props(obj: self, count: 0, limit: limit)
}
private func props(obj: Any, count: Int, limit: Int) -> [String: Any] {
let mirror = Mirror(reflecting: obj)
var result: [String: Any] = [:]
for (property, value) in mirror.children {
var val = value
if let values = value as? [Loopable] {
var vals = [Any]()
for val in values {
vals.append(val.allProperties())
}
val = vals
}
guard let prop = property else { continue }
if limit == count {
result[prop] = unpackValue(val)
} else {
let subResult = props(obj: val, count: count + 1, limit: limit)
result[prop] = subResult.isEmpty ? unpackValue(val) : unpackValue(subResult)
}
}
return result
}
private func unpackValue(_ value: Any) -> Any {
var unpacked: Any
if let value = value as? [String: Any] {
if let v1 = value["value"] {
unpacked = v1
} else if let v2 = value["some"] {
unpacked = v2
} else {
unpacked = value
}
} else {
unpacked = value
}
if let deeper = unpacked as? [String: Any] {
return unpackValue(deeper)
} else {
return unpacked
}
}
}