Once-a-year Black Friday deals are coming. Read more.
Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Using Custom debug descriptions to improve debugging

Custom debug descriptions can help you debug your own custom objects, structs, errors, and other types. Whenever you print out an object you might end up with basic information that doesn’t really help you solve your issue. Printing out a struct shows you all the values while you might only be interested in one of those properties in most cases.

Swift provides a default debugging textual representation for any type by using the String(reflecting:) initializer. The debugPrint(_:) method is used for types that don’t provide their own representation. Even though this already works great in most cases, you could benefit more and speed up debugging by implementing your own custom representation for types you often interact with.

In this article, we’re making use of the print and debugPrint methods which are described in more detail later on. Note that using po object in an LLDB debug session aligns with using debugPrint in code.

The default textual representation provided in Swift

Before we start adding improvements it’s good to know what Swift provides us by default. For this, we go over a few often used types and we use the print() and debugPrint methods. Those two print methods are almost the same but the debugPrint method allows you to optionally print out more verbose information. We’ll go over a few examples later on.

We start the examples with the following BlogPost entity which is defined as a class:

final class BlogPost {
    let title: String
    let body: String

    init(title: String, body: String) {
        self.title = title
        self.body = body
    }
}

Once we print this out, we get the following results:

let blogPost = BlogPost(title: "Improved debugging", body: "Help yourself in those hard times.")
print(blogPost) 
// Prints: BlogPost
debugPrint(blogPost) 
// Prints: BlogPost

Changing it to a struct already improves the print statements:

struct Post {
    let title: String
    let body: String
}
let post = Post(title: "Improved debugging", body: "Help yourself in those hard times.")
print(post) 
// Prints: Post(title: "Improved debugging", body: "Help yourself in those hard times.")
debugPrint(post) 
// Prints: Post(title: "Improved debugging", body: "Help yourself in those hard times.")

This is probably enough is most of the cases.

This does not count for printing out instances of NSObject, like UIViewController that inherits from this type. It often results in unuseful information:

final class BlogPostViewController: UIViewController { }
let viewController = BlogPostViewController()
print(viewController) 
// Prints: <BlogPostViewController: 0x7ff77ac0c720>
debugPrint(viewController) 
// Prints: <BlogPostViewController: 0x7ff77ac0c720>

You can see that there’s room for improvement here as we have no clue what this view controller represents. The default custom debug descriptions aren’t valuable to improve our debugging.

Stay updated with the best of Swift & SwiftUI

Join over 20,005 Swift developers in SwiftLee Weekly for exclusive tips and updates. Don’t miss out – subscribe now:

You can always unsubscribe, no hard feelings.

Using the CustomDebugStringConvertible for improved debug prints

The CustomDebugStringConvertible protocol comes in place to improve the debugging logs for our custom objects. It’s used by both the print and debugPrint method, unless the CustomStringConvertible protocol is used. To explain this, we’re adding conformance to both protocols for our view controller instance.

We start by defining the view controller and initialize it with a BlogPost instance.

final class BlogPostViewController: UIViewController {
    let post: BlogPost

    init(post: BlogPost) {
        self.post = post
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
let blogPost = BlogPost(title: "Improved debugging", body: "Help yourself in those hard times.")
let viewController = BlogPostViewController(post: blogPost)

Printing this instance results in the following logs:

print(viewController) 
// Prints: <BlogPostViewController: 0x7fb390c127f0>
debugPrint(viewController) 
// Prints: <BlogPostViewController: 0x7fb390c127f0>

We start by adding conformance to the CustomDebugStringConvertible protocol by returning the blog post title and body:

extension BlogPostViewController: CustomDebugStringConvertible {
    var debugDescription: String {
        "\(self) represents the post with title \"\(blogPost.title)\" and body \"\(blogPost.body)\""
    }
}

Using this code example will result in the following error:

Redundant conformance of ‘BlogPostViewController’ to protocol ‘CustomDebugStringConvertible’

This is because of the fact that NSObject instances already conform to the CustomDebugStringConvertible protocol. Therefore, we can leave out the protocol definition and simply override the debugDescription property:

extension BlogPostViewController {
    override var debugDescription: String {
        "\(self) represents the post with title \"\(blogPost.title)\" and body \"\(blogPost.body)\""
    }
}

Printing out the same instance again will give us much better information:

print(viewController) 
// Prints: <BlogPostViewController: 0x7fa013406810> represents the post with title "Improved debugging" and body "Help yourself in those hard times."
debugPrint(viewController) 
// Prints: <BlogPostViewController: 0x7fa013406810> represents the post with title "Improved debugging" and body "Help yourself in those hard times."

Adding custom debug descriptions to instances of NSObject is extra valuable as the default representation is oftentimes not useful at all.

Hiding sensitive data in non-debug prints

Sometimes, you only want to print out certain data during a debug session. Regular print statements might end up in logs that are visible to the user if that’s something your app supports. For example, we’re using the Diagnostics framework to help us debug user sessions in which all our logs could potentially be visible for users reading that report.

We can make use of the CustomStringConvertible to add a distinction between logs. For example, we might want to hide the body of an article during regular prints:

extension BlogPostViewController {
    override var description: String {
        "\(super.description) represents the post with title \"\(blogPost.title)\""
    }
}

Note that we’re not adding the protocol inheritance directly as this is already done by the NSObject, once again. Printing out the same instance again shows us two different descriptions for print and debugPrint:

print(viewController) 
// Prints: <BlogPostViewController: 0x7fa42fe09bf0> represents the post with title "Improved debugging"
debugPrint(viewController) 
// Prints: <BlogPostViewController: 0x7fa42fe09bf0> represents the post with title "Improved debugging" and body "Help yourself in those hard times."

As you can see, the print method is making use of the description property while the debugPrint is using the debugDescription property. This distinction is both important and useful to know when working with those protocols to create custom debug descriptions.

Note that we’re using super.description here as using self would result in using the reflection API, which uses description, which uses our custom description, which is an infinite loop (just like this sentence starts to look like an infinite loop). It’s useful to still print out the instance so that you know the type of the object and the memory address.

 The above example could’ve been improved, even more, by reusing the description value:

extension BlogPostViewController {
    override var description: String {
        "\(super.description) represents the post with title \"\(blogPost.title)\""
    }

    override var debugDescription: String {
        "\(self) and body \"\(blogPost.body)\""
    }
}

Improved logging for Error types in Swift

The same protocol can be used for any type in Swift, like errors. We can add improved logging support for custom Error types and show a more detailed description that explains the failure:

enum BlogPostValidationError: Error {
    case emptyTitle
}
print(BlogPostValidationError.emptyTitle) 
// Prints: emptyTitle

extension BlogPostValidationError: CustomDebugStringConvertible {
    var debugDescription: String {
        switch self {
        case .emptyTitle:
            return "Validation failed: The blog post can not be empty"
        }
    }
}
print(BlogPostValidationError.emptyTitle) 
// Prints: Validation failed: The blog post can not be empty

Adding improved logging to structure types

Even though structs have a great representation by default, we might still want to improve those as well by adding custom debug descriptions that print out values in a way that makes more sense:

extension Post: CustomDebugStringConvertible {
    var debugDescription: String {
        """
        Title:
        \(title)

        Body:
        \(body)
        """
    }
}
print(blogPost)
// Prints:
// Title:
// Improved debugging
//
// Body:
// Help yourself in those hard times.

Conclusion

Debugging is already time-consuming in most cases. Anything that can help us solve our issues faster and more easily can be of great value. We can distinguish between debug prints and regular prints to only print out data that is non-sensitive for the users that read our logs. Doing so is extra valuable as po object in an LLDB session makes use of the same debugDescription.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.