Is it possible to iterate over properties of a struct in Swift?
I need to register cells-reuse identifiers in a view controller that makes use of many different cel
Using hris.to's awesome answer, I wanted to provide a Swift 3 answer that's more to the point and doesn't use singletons.
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]
Here is an example of iterating over struct properties (reuse identifiers of UITableViewCells and the corresponding NIB-names) using Swifts tuple feature. This is useful if you like organizing your cells in nib files and have a UIViewController that makes use of many different cell types.
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)]
}
With that struct it is easy to register all cell types using a for-loop:
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
I made a recursive function based on @John R Perry's solution that goes deeper into properties that are objects. It also takes an parameter to limit how many levels deep it goes (default is Int.max
) to help prevent stackoverflow's:
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
}
}
I got rid of the check for if the object is a class
or struct
because that the parameter not being a class
or struct
is the base case of the recursive function, and it was easier to handle it manually than with errors.
Testing it:
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())
prints:
["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]
(Dictionaries are unordered, so if you get a different order, that's why)
Although old question, with Swift evolving this question has new answer. I think that you approach is way better for the described situation, however original question was how to iterate over struct properties, so here is my answer(works both for classes and structs)
You can use Mirror Structure Reference. The point is that after calling reflect
to some object you get it's "mirror" which is pretty sparingly however still useful reflection.
So we could easily declare following protocol, where key
is the name of the property and value
is the actual value:
protocol PropertyLoopable
{
func allProperties() throws -> [String: Any]
}
Of course we should make use of new protocol extensions to provide default implementation for this protocol:
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
}
}
So now we can loop over the properties of any class
or struct
with this method. We just have to mark the class as PropertyLoopable
.
In order to keep things static(as in the example) I will add also a singleton:
struct ReuseID: PropertyLoopable {
static let instance: ReuseID = ReuseID()
}
Whether singleton used or not, we could finally loop over the properties like follows:
do {
print(try ReuseID.instance.allProperties())
} catch _ {
}
And that's it with looping struct properties. Enjoy swift ;)
Knowing that in Swift 1.2 you could use reflect()
, and since Swift 2 you can use Mirror, here is an addition to hris.to's answer for Swift 3 and 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
}
}
Usage on any struct or class:
extension NSString: Loopable {}
print("hello".allProperties)
// ["_core": Swift._StringCore(_baseAddress: Optional(0x00000001157ee000), _countAndFlags: 5, _owner: nil)]
Now there's a much easier way to do this:
1: Create an Encodable protocol extension:
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: Make your struct/class conform to Encodable protocol
struct MyStruct: Encodable {...}
class MyClass: Encodable {...}
And then you can get a Dictionary representing your struct/class instance at any time:
var a: MyStruct
var b: MyClass
print(a.dictionary)
print(b.dictionary)
And then you can loop through the keys:
for (key, value) in a.dictionary { ... }
for (key, value) in b.dictionary { ... }