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.
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!