Easier Swift Debugging with Mirrors

18 February 2016

As we all know, Swift is the future of iOS app development. First and foremost, Swift has been designed to be easy to learn, concise, and fast. With the release of Swift 2, Apple addressed many of the issues developers faced with Swift 1, and refined the language with syntax improvements, protocol extensions, and a modern approach to error handling. With these additions, along with the REPL and Xcode Playgrounds, now is a great time for anyone still on the fence about Swift to dive in and start seeing what all of the fuss is about.

However, there is still one area of day-to-day development where Swift continues to fall short compared to Objective C, and that is during debugging. Thanks to Swift's built-in safety you will likely, once up to speed, spend less time using the debugger when compared to developing with Objective C. But when first getting started, or when the inevitable time comes when you need to figure out the source of a bug, the tooling available for Swift developers can leave a lot to be desired. In particular, trying to find out information about objects can be a trying experience. It is this problem that we will take a look at today.

Reflection

One of the extensions to Swift that came along in version 2 was improved reflection support. Reflection has many uses, but one that is often overlooked is how it can be used to make our lives easier during debugging.

Consider, for example, the Handoff support added recently to our Counties sample app. We built our Handoff support using NSUserActivity. This class handles its task very nicely, but what if we need to debug our Handoff support? Perhaps we're not seeing the behaviour we expect and we want to take a closer look at the information we're receiving to see what's going on. Pretty simple, right? Just 'po' the object in the debugger:

(lldb) po userActivity
▿ Optional(<NSUserActivity: 0x7fe7d4405d50>)

Oh, excellent. That's really useful. Is this the end of the road? No - we could start po'ing individual properties of the user activity but that makes for pretty slow progress. And what if we have to debug another user activity? We'll have to start printing properties again, wasting even more time. Fortunately, there is a better way.

CustomReflectable

The CustomReflectable protocol is our saviour. It is this protocol that is used internally when po'ing an object to provide information to be displayed in the debugger. To quote the documentation:

Instances of any type can be Mirror(reflect:)'ed upon, but if you are not satisfied with the Mirror supplied for your type by default, you can make it conform to CustomReflectable and return a custom Mirror.

The problem we saw with NSUserActivity is that NSObject has no default implementation of CustomReflectable, so instead we are only given the class name and memory address of the object in question. If we want more information to be displayed we have to return a custom mirror by implementing the CustomReflectable protocol. This is a simple process as there is only one method to implement: customMirror(). We can add this as an extension on NSUserActivity to give the debugger access to its properties:

extension NSUserActivity : CustomReflectable {
    public func customMirror() -> Mirror {
        let children = DictionaryLiteral<String, Any>(dictionaryLiteral:
            ("activityType", activityType),
            ("title", title),
            ("userInfo", userInfo.debugDescription),
            ("requiredUserInfoKeys", requiredUserInfoKeys),
            ("needsSave", needsSave),
            ("webpageURL", webpageURL),
            ("expirationDate", expirationDate.debugDescription),
            ("keywords", keywords),
            ("supportsContinuationStreams", supportsContinuationStreams))
        return Mirror(NSUserActivity.self, children: children, displayStyle: .Class, ancestorRepresentation: .Suppressed)
    }
}

The important bit here is the children dictionary literal. This is what determines what will be shown in the debugger, including the order in which it is specified. Having added this extension, let's try inspecting a user activity again:

(lldb) po userActivity
▿ Optional(<NSUserActivity: 0x7fb890d2a340>)
▿ Some : <NSUserActivity: 0x7fb890d2a340>
- activityType : "com.darjeeling.counties.handoff.countydetails"
- title : nil
- userInfo : "Optional([CountyName: Cambridgeshire])"
- requiredUserInfoKeys : 0 elements
- needsSave : true
- webpageURL : nil
- expirationDate : ""
- keywords : 0 elements
- supportsContinuationStreams : false

Much better! We can now debug our user activities with ease.

Note that this gives us complete control over what is displayed when printing an object's contents in the debugger.

A Note on Structs

The CustomReflectable protocol can also be conformed to by structs. In fact, structs have a default implementation which is far more useful than the built-in behaviour we saw with objects earlier:

(lldb) po county
▿ County
- name : "Berkshire"
- population : 863800
- latitude : 51.416667
- longitude : -1.0

This is the result of po'ing a County struct in the debugger. Without any work on our part, this prints out all of the struct's members by representing them using the Child type as defined by Swift's Mirror:

public typealias Child = (label: String?, value: Any)

This is all very nice, but this default implementation will also include a struct's private members. If you are developing a framework, or for some other reason need to make sure that certain fields stay private, then it would be prudent to extend your structs with CustomReflectable and provide a custom Mirror that conceals these internal details.

Conclusion

There are numerous useful programming approaches that are made available to developers by a strong reflection API, which we will likely see become more popular in Swift as it matures. Today we have seen a simple use of reflection and how it can make our lives much simpler during debugging sessions - a time when clarity and quick access to information can make the difference between hours of misery and a swift resolution to a problem. To see it in action checkout the sample project, set some breakpoints, start Handing off, and enjoy the rich debug information enabled by using this technique.