Hallo vrienden, Leo hier. The topic is about code reflection in Swift and what you should consider when using a solution like this in your codebase.
We will talk about exotic magic in programming, yes everyone, we will talk about Reflection. I particularly find this naming perfect for the technique because this is a computer science term that describes the ability of a process to examine, introspect, and modify its structure and behavior.
And more brilliant than the naming reflection is the name of the Swift API that enables us, developers, to do that the name of Swift API is Mirror. Really good, isn’t it?
Like everything in life, great power should come with great responsibilities and that is exactly what we will discuss today.
Cleanup your mirrors, and let’s code! But first… we have a special thanks and the Painting of the day!
Special Thanks
A special thanks to Pedro Freddi that sent coffees to me through Buy me a coffee, you are awesome, and this motivates me more and more!
Painting of the Day
The painting I chose today could not be anything else than Narcissus by the legendary Italian Baroque master Caravaggio, painted circa 1597–1599. It is housed in the Galleria Nazionale d’Arte Antica in Rome.
The painting was originally attributed to Caravaggio by Roberto Longhi in 1916. This is one of only two known Caravaggios on a theme from Classical mythology, although this is due more to the accidents of survival than the artist’s oeuvre. Narcissus, according to the poet Ovid in his Metamorphoses book, is a handsome youth who falls in love with his own reflection. Unable to tear himself away, he dies of his passion, and even as he crosses the Styx, the river that divided earth and the otherworld, he continues to gaze at his reflection.
I chose this painting because as Narcissus we are going to use the mirror a lot today.
The Problem – Code Reflection in Swift using Mirror
Imagine that you have a code structure (can be a class or a struct) that you want to parse all values of all properties automatically, you don’t want to each time anyone adds a new property you have to add something new manually.
In our example, we want to create a protocol that represents a set of colors that one thing can have and I want to get a list of all colors.
Let’s check the example below:
protocol MyColors { var color1: UIColor { get } var color2: UIColor { get } var color3: UIColor { get } var allColors: [UIColor] { get } }
Easy right? So now we can implement it, for example like this:
struct MyLightColors: MyColors { var color1: UIColor = .red var color2: UIColor = .white var color3: UIColor = .blue var allColors: [UIColor] { [color1, color2, color3] } } struct MyDarkColors: MyColors { var color1: UIColor = .white var color2: UIColor = .black var color3: UIColor = .white var allColors: [UIColor] { [color1, color2, color3] } }
Nice and we are pretty much done. But… this code can be improved. As you can see, leaving the protocol it is, will force each one of the conforming classes/structs to implement its own set of allColors
.
We can improve that code by putting the computed var as a default implementation of the protocol, check below:
extension MyColors { var allColors: [UIColor] { [color1, color2, color3] } }
Now the conforming structs only need to provide the colors, as we intended at first:
struct MyLightColors: MyColors { var color1: UIColor = .red var color2: UIColor = .white var color3: UIColor = .blue } struct MyDarkColors: MyColors { var color1: UIColor = .white var color2: UIColor = .black var color3: UIColor = .white }
We have a drawback about this solution though. Every time someone adds a new color, he/she will need to remember to also add it into the allColors
array. For classes that won’t change, for example, if you are sure that your struct/class will not change, it is enough. But imagine that you add a new color property each week or month?
Without using the Mirror
API this is the best I could get. From now on let’s continue to dive into this solution using mirror API and how you can have a more generic approach to this problem.
Using Mirror in Swift
The documentation says that a mirror object describes the parts (properties) that make up a particular instance, such as the instance’s stored properties, collection or tuple elements, or its active enumeration case.
You can put any object into the mirror API and it will give you access to the name(label) and the value of all the object properties, even if they are private. This is a superpower that you should be very careful when using, if the people that wrote the object doesn’t intend to expose some properties, maybe you should think twice about accessing it using Mirror superpowers.
For example:
let lightColors = MyLightColors() let mirror = Mirror(reflecting: lightColors)
With Xcode 14 beta 2, the options for that mirror
object are:
The children
property is what we are looking for. It holds a Lazy sequence of all properties of a Swift object. Let’s parse the object with Mirror API and see the results:
let lightColors = MyLightColors() let mirror = Mirror(reflecting: lightColors) mirror.children.forEach { print($0) }
Resulting in:
Now you can see where we are getting with this solution. In your MyColors
extension put replace the old computed variable code with this new one using Mirror reflection:
extension MyColors { var allColors: [UIColor] { let mirrorSelf = Mirror(reflecting: self) let allColorPropertiesValues = mirrorSelf.children.compactMap { $0.value as? UIColor } return allColorPropertiesValues } }
But what that code is doing? Let’s dissect it:
- First, we are creating a
Mirror
object of the self. That means that we are creating aMirror
object of the actual object that conforms to the protocolMyColors
. - We are iterating over all its children properties and for each one of them we are checking if we can downcast the value of the property to the
UIColor
class and add it to theallColorPropertiesValues
array. - Returning the array
allColorPropertiesValues
.
Trade-offs of presented Mirror API Solution
The advantage of using this way is that every time anyone adds a new color property to the protocol, we don’t need to do anything with the allColors computed variable. This way we will never have to worry to update that anymore. This makes your code more reliable and easy to maintain.
Like most coding decisions, this comes with some drawbacks. The disadvantage is that now the code works magically. Imagine that we add another UIColor
to the properties in and we DON’T want to parse it into the allColors
property.
Or imagine that instead of UIColor
the properties became CGColor
everything stops working again, and unless you have unit tests ( and you should ) covering those cases, probably you will take time to notice that.
Mind those parsing properties from an object is a very powerful tool but it has to be used consciously and it is not the answer for everything. Mirror API is there to be used for specific use cases, this way it is a brilliant tool in your toolset.
Summary – Code Reflection in Swift
Today we explored a neat solution using Apple Mirror API to use reflection techniques in our code. You need to be very conscious when using that technique and it is a very powerful one because it enables you to do things that otherwise are impossible, for example reading private properties.
If you liked this post, probably you also like [how to use Swift APIs to natively make unit conversions](https://holyswift.app/using-native-swift-to-make-units-conversions).
That’s all my people, today we finished the Architecture and iOS Tooling article closing the series about the beginning of iOS development. I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing!
You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.
Thanks for reading and… That’s all folks.
Credits: