Reflection in Swift 5

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.