Reflection in practice
Reflection technique may be useful to build quasi generic functions that operate at runtime. It may be quasi type safe, though it uses runtime too - as such can't be optimised/validated during compilation. With Objective-C we used <runtime/objc.h>
and do all the things. In Swift it's just complicated.
I'm not going to explain here in details what the reflection is and how it works, there are cool projects already (check out Mirror) and plenty of blog posts about this. What I'll demonstrate here is simply "for fun", and educational purpose.
Goal
The goal is to calculate (dynamically, without anonymous strings) sum of properties (variables) out of struct
or class
.
For this excercise I'll work with food. The Meal object consisting of nutrients, and I'll calculate sum of nutrients in collection of meals.
The meal
Meal
describes single meal. Meal consists of calcium and fat, which is defined by NutrientsType
protocol
struct Meal: NutrientsType {
var calcium: Int
var fat: Int
}
NutrientsType
to abstract code a little, for any meal I defined NutrientsType
there is protocol with defined nutrients and with function total(...)
protocol NutrientsType {
var calcium: Int { get }
var fat: Int { get }
}
next thing is NutrientsType.total(...)
function. This function sum nutrient values from the list of meals and return the value. With help of type Mirror
I can access to the list of variables of struct or class, then use it to access the value.
extension NutrientsType {
static func total(meals: [Self], nutrient: Nutrients) -> Int {
return meals.reduce(0) { (sum, meal) -> Int in
for child in Mirror(reflecting: meal).children where child.label == nutrient.rawValue {
let valueMirror = Mirror(reflecting: child.value)
if valueMirror.subjectType == Optional<Int>.self {
return sum + ((valueMirror.children.first?.1 as? Int) ?? 0)
} else {
return sum + (child.value as! Int)
}
}
return sum
}
}
}
Nutrients
To escape from string identifiers (we don't like anonymous string labels in Swift) I defined possible nutrients, like Calcium and Fat, with enum:
enum Nutrients: String {
case calcium, fat
}
this way I can validate anonymous input data: I can't use any string label to identify variable in my struct or class.
All together
The best part is here. For given list of meals I calculate sums:
let meal1 = Meal(calcium: 3, fat: 30)
let meal2 = Meal(calcium: 13, fat: 20)
Meal.total([meal1, meal2], nutrient: .fat)
need more magic? there you go. Defining this simple extension to Array type:
extension Array where Element: NutrientsType {
func total(nutrition: Nutrients) -> Int {
return Element.total(self, nutrition: nutrition)
}
}
I can calculate total fat value right of the array of meals
[meal1, meal2].total(.fat)
Conclusion
I don't know if this is that useful. I just found it interesting to know. I used reflection a lot in Objective-C but with Swift I'm trying to avoid that, or use demonstrated above techniques to hide dynamic nature of the code. What you see here is mixture of runtime and compile-time approach. Enum will check if input field names are valid, then it is used in runtime to map the values to actual struct or class. The ugliest part is dealing with Mirror
, which is the reflection itself.