Dynamic Features in Swift

In this tutorial, you’ll learn to use dynamic features in Swift to write clean code, create code clarity and resolve unforeseen issues quickly. By Mike Finney.

Leave a rating/review
Download materials
Save for later
Share

As a busy Swift developer, you have needs that are specific to your world yet common to all. You want to create clean-looking code, learn what’s going on in your code at a glance and resolve unforeseen issues quickly.

This tutorial ties together the dynamic and flexible parts of Swift to meet those needs. Using the latest Swift technology, you’ll learn how to customize output to your console, hook into third-party object state changes, and use some sweet syntactical sugar to write cleaner code.

Specifically, you will learn about:

  • Mirror
  • CustomDebugStringConvertible
  • Key-value Observing with Key Paths
  • Dynamic Member Lookup
  • Related technologies

Most of all, you’ll have a doggone good time!

This tutorial requires Swift 4.2 or later. Until Xcode 10 is released, you must download the latest Xcode 10 Beta or install the latest Swift 4.2 snapshot.

Also, you must have an understanding of basic Swift types. The Getting to Know Enums, Structs and Classes in Swift tutorial is a great place to start. Although not strictly required, you may want to look into Implementing Custom Subscripts in Swift as well.

Getting Started

Before doing anything, download the starter and final projects by clicking on the Download Materials button at the top or the bottom of the tutorial. Unzip the downloaded file.

You’ll be happy to know that all the code you need to let you focus on learning the dynamic features of Swift is already written for you! Like walking with a friendly guide dog, this tutorial will guide you through everything in the starter code.

In the starter code directory named DynamicFeaturesInSwift-Starter, you’ll see three playground pages: DogMirror, DogCatcher and KennelsKeyPath. The playground is set to run on macOS. This tutorial is platform-agnostic and only focuses on the Swift language.

Reflecting on Mirror and Debug Output

Whether you’re tracking down an issue or just exploring running code, uncluttered information in the console makes all the difference. Swift offers many ways of customizing console output and capturing crucial events. For customizing output, it doesn’t get any deeper than Mirror. Swift offers more power than the strongest sled dog to pull you out of the icy cold land of confusion!

Before learning more about Mirror, you’ll first write some customized console output for a type. This will help you more clearly see what’s going on.

CustomDebugStringConvertible

Open DynamicFeaturesInSwift.playground in Xcode and go to the **DogMirror** page.

In honor of all those cute little dogs that get lost, caught by a dog catcher and then reunited with their owners, this page has a Dog class and DogCatcherNet class. Focus on the DogCatcherNet first.

Since the lost doggies out there must be caught and reunited with their owners, dog catchers must be supported. The code you write in the following project will help dog catchers evaluate the quality of nets.

In the playground, look at the following:

enum CustomerReviewStars { case one, two, three, four, five }
class DogCatcherNet {
  let customerReviewStars: CustomerReviewStars
  let weightInPounds: Double
  // ☆ Add Optional called dog of type Dog here

  init(stars: CustomerReviewStars, weight: Double) {
    customerReviewStars = stars
    weightInPounds = weight
  }
}
let net = DogCatcherNet(stars: .two, weight: 2.6)
debugPrint("Printing a net: \(net)")
debugPrint("Printing a date: \(Date())")
print()

The DogCatcherNet has two properties: customerReviewStars and its weightInPounds. Customer review stars reflect the customers’ feelings about the net product. The weight in pounds tells the dog catchers what burden they will experience lugging a net around.

Run the playground. The first two lines you should see are similar to the following:

"Printing a net: __lldb_expr_13.DogCatcherNet"
"Printing a date: 2018-06-19 22:11:29 +0000"

As you can see, the debug output in the console prints something related to a net and a date. Bless its heart; the output from the code looks like it was made by a robotic pet. This pet tried hard, but it needs help from us humans. As you can see, it’s printing out extra information such as “__lldb_expr_.” Printing out the date provides something more useful. It’s up in the air as to whether or not this is enough to help you track down a problem that’s been dogging you.

To increase your chances of success, you need to apply some console output customization basics using CustomDebugStringConvertible magic. In the playground, add the following code right under ☆ Add Conformance to CustomDebugStringConvertible for DogCatcherNet here:

extension DogCatcherNet: CustomDebugStringConvertible {
  var debugDescription: String {
    return "DogCatcherNet(Review Stars: \(customerReviewStars),"
      + " Weight: \(weightInPounds))"
  }
}

For something small like DogCatcherNet, a type can conform to CustomDebugStringConvertible and provide its own debug description using the debugDescription property.

Run the playground. Except for a date value difference, the first two lines should include:

"Printing a net: DogCatcherNet(Review Stars: two, Weight: 2.6)"
"Printing a date: 2018-06-19 22:10:31 +0000"

For a larger type with many properties, this approach comes with the cost of explicit boilerplate to type. That’s not a problem for one with dogged determination. If short on time, there are other options such as dump.

Dump

How to avoid needing to add boilerplate code manually? One solution is to use dump. dump is a generic function that prints out all the names and values of a type’s properties.

The playground already contains calls that dump out the net and Date. The code looks like this:

dump(net)
print()

dump(Date())
print()

Run the playground. The console output looks something like:

▿ DogCatcherNet(Review Stars: two, Weight: 2.6) #0
  - customerReviewStars: __lldb_expr_3.CustomerReviewStars.two
  - weightInPounds: 2.6

▿ 2018-06-26 17:35:46 +0000
  - timeIntervalSinceReferenceDate: 551727346.52924

Due to the work you’ve done so far with CustomDebugStringConvertible, the DogCatcherNet looks better than it otherwise would. The output contains:

DogCatcherNet(Review Stars: two, Weight: 2.6)

dump also spits out each property automatically. Great! It’s time to make those properties more readable by using Swift’s Mirror.

Swift Mirror

Mirror lets you display values of any type instance through the playground or the debugger at runtime. In short, Mirror‘s power is introspection. Introspection is a subset of reflection.

Creating a Mirror-Powered Dog Log

It’s time to create a Mirror-powered dog log. To help with debugging, it’s ideal to display the values of the net to the console through a log function with custom output complete with emoticons. The log function should be able to handle any item you pass it.

Mike Finney

Contributors

Mike Finney

Author

Jayven Nhan

Tech Editor

Vladyslav Mytskaniuk

Illustrator

Morten Faarkrog

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.