是否有可能在Swift中迭代结构的属性?
我需要在视图控制器中注册单元重用标识符,该控制器使用许多不同的单元格类型(单元格组织在不同的nib文件中)。所以我的想法是将所有重用标识符和相应的nib文件作为静态元组属性(reuseID,nibName)放在结构中。但是,如何迭代所有这些以使用tableView注册单元格?
我已经尝试了一些东西(请参阅下面的答案)。但有没有更简单的方法来做到这一点,例如不将每个属性都放在数组中?
答案 0 :(得分:20)
虽然老问题,随着Swift的发展,这个问题有了新的答案。我认为你所采用的方法对于描述的情况更好,但原始问题是如何迭代结构属性,所以这是我的答案(适用于类和结构)
您可以使用 Mirror Structure Reference 。重点是,在将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
}
}
现在我们可以使用此方法循环遍历任何 class
或struct
的属性。我们只需将该类标记为PropertyLoopable
。
为了保持静态(如示例所示),我还将添加一个单例:
struct ReuseID: PropertyLoopable {
static let instance: ReuseID = ReuseID()
}
无论是否使用单例,我们最终都可以遍历以下属性:
do {
print(try ReuseID.instance.allProperties())
} catch _ {
}
这就是循环结构属性。快乐享受;)
答案 1 :(得分:9)
使用 hris.to 的精彩答案,我想提供一个 Swift 3 的答案,这个答案更加重要,并且没有&#39 ; t使用单身人士。
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]
答案 2 :(得分:3)
知道在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)]
答案 3 :(得分:2)
这是使用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 ")
}
}
答案 4 :(得分:1)
现在有一种更简单的方法:
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:使您的struct / class符合可编码协议
struct MyStruct: Encodable {...}
class MyClass: Encodable {...}
然后您可以随时获取一个表示您的struct / class实例的Dictionary:
var a: MyStruct
var b: MyClass
print(a.dictionary)
print(b.dictionary)
然后您可以循环浏览按键:
for (key, value) in a.dictionary { ... }
for (key, value) in b.dictionary { ... }
答案 5 :(得分:0)
我基于@John R Perry的解决方案创建了一个递归函数,该函数更深入到对象的属性中。它还需要一个参数来限制其深度(默认为Int.max
),以防止堆栈溢出:
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]
(词典是无序的,所以如果您获得不同的顺序,这就是为什么)