
Reflection in Swift 5
Many languages have build-in reflection support. According to Wiki reflection is the ability of a process to examine, introspect, and modify its own structure and behaviour. Type introspection is the ability of a program to examine the type or properties of an object at runtime.
Compiled languages rely on their runtime system to provide information about structure and behavior.
Reflection can be quite handy for solving such tasks as serialising and deserialising objects, creating mock objects for testing, gathering debug information, etc.
If you use Swift for developing with Apple platforms, you can run your code in Objective-C runtime (by interacting with obj-c code or inherit your swift classes from NSObject) or Swift runtime. This is an important point.
Objective-C runtime has rich supports of reflection. You can fetch all instance variables and selectors (e.g. methods) from your objective-c classes. You can add new methods to it, you can emulate adding a new variable to the object (using associated objects), you can exchange methods of a class, and so on.
Swift runtime supports only introspection. You can only read instance’s stored properties, collection or tuple elements, or its active enumeration case. There are a lot of articles about Objective-C runtime. But in this article, we will focus only on Swift runtime.
Mirror API
Introspection in Swift is done via the Mirror API, which is part of the standard Swift API.
Fetching stored properties from an instance is easy. We just have to create a Mirror struct, passing the target instance which we want to introspect. Then, using Mirror’s ‘children’ property we can iterate over the properties of the inspected instance.
So, let’s try an example. (All examples for this article were made in Playground.)
We will declare a class DocFolder, which represents a folder in the file system. For now, it has 2 properties.
class DocFolder {
public let url: URL
private let identifier: String
init(url: URL) {
self.url = url
identifier = UUID().uuidString
}
}
let folderURL = URL(fileURLWithPath: "/docs")
let folder = DocFolder(url: folderURL)
Now, create a Mirror, using the constructor init(reflecting subject: Any) and list all properties of the DocFolder instance
let mirror = Mirror(reflecting: folder)
for item in mirror.children {
print("\(item.label ?? "") : \(item.value)")
}
and the output is
url : file:///docs
identifier : E3210019-A27F-474F-8B16-6DB7B4A240FF
As we see, Mirror listed all properties of the DocFolder, even the private one.
So, what is that mirror.children? It is actually a collection of Mirror.Child items. Mirror.Child is actually a typealias for a tuple (label: String?, value: Any)
where label – name of the property and value – the actual value of the property.
What type of properties can we reflect?
The documentation says: “A mirror describes the parts that make up a particular instance, such as the instance’s stored properties, collection or tuple elements, or its active enumeration case”. So, we can reflect any property type, except computed and lazy properties.
What is Mirror used for?
First of all, debugging. When you try to describe an instance type, like “doc folder is \(folder)”, or String(describing:folder) swift checks if the type conforms to CustomStringConvertible or CustomDebugStringConvertible. If not, the default fallback is to use Mirror to print the object.
Let’s try this.
"documents folder is \(folder)"
output:
"documents folder is __lldb_expr_72.DocFolder"
Not very informative. Can we do something about it? Absolutely yes!
The CustomReflectable protocol
Mirror has a displayStyle property of type Mirror.DisplayStyle, which is actually an enum. It defines, how Mirror displays the reflected instance, when you, for example, describe the object. The display style for class Mirror is ‘class’
mirror.displayStyle
output:
class
By default, ‘class’ style just displays the module and class names. If we want to change the display style in order to display also stored properties of the class, we need to provide our custom Mirror object. Swift allows us to do this by conforming our type to CustomReflectable protocol.
extension DocFolder: CustomReflectable {
var customMirror: Mirror {
return Mirror(self,
children: ["folder URL" : url,
"id" : identifier],
displayStyle: .struct)
}
}
"documents folder is \(folder)"
output:
"documents folder is DocFolder(folder URL: file:///docs, id: "0B142988-12DF-4A86-935D-38E26DA7EC96")"
More debug info. Great!
Some important notes:
- We provided our list of variables, that should be accessible via the Mirror object. We can change the variable list, their names and values.
- We provided the display style ‘struct’ instead of ‘class’.
We can also play a little with different display styles:
style | result of String(reflecting: folder) |
struct | DocFolder(folder URL: file:///docs, id: “10DE3B53-83F2-402F-88D5-17CB44BFB535”) |
class | __lldb_expr_136.DocFolder |
tuple | (folder URL: file:///docs, id: “7D55D479-5A5F-4273-98C8-67269DECE007”) |
set | __lldb_expr_140.DocFolder |
none | __lldb_expr_144.DocFolder |
optional | file:///docs |
enum | DocFolder(file:///docs) |
dictionary | __lldb_expr_150.DocFolder |
collection | __lldb_expr_152.DocFolder |
Conclusion
Reflection in Swift is very limited. Swift supports only introspection in read only mode. Introspection is done via Mirror API which is very simple and easy to use. It is also possible to provide a custom Mirror struct. In this way we can customize what Introspection shows.
Even these small features can be very handy for debugging and other purposes.