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
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Creating a Mirror

It’s time to create a log function that uses a mirror. To start, add the following code right under ☆ Create log function here:

func log(itemToMirror: Any) {
  let mirror = Mirror(reflecting: itemToMirror)
  debugPrint("Type: 🐶 \(type(of: itemToMirror)) 🐶 ")
}

This creates the mirror for the passed-in item. A mirror lets you iterate over the parts of an instance.

Add the following code to the end of the log(itemToMirror:):

for case let (label?, value) in mirror.children {
  debugPrint("⭐ \(label): \(value) ⭐")
}

This accesses the children property of the mirror, gets each label-value pair, then prints them out to the console. The label-value pair is type-aliased as Mirror.Child. For a DogCatcherNet instance, the code iterates over the properties of a net object.

To clarify, a child of the instance being inspected has nothing to do with a superclass or subclass hierarchy. The children accessible through a mirror are just the parts of the instance being inspected.

Now, it’s time to call your new log method. Add the following code right under ☆ Log out the net and a Date object here:

log(itemToMirror: net)
log(itemToMirror: Date())

Run the playground. You’ll see at the bottom of the console output some doggone great output:

"Type: 🐶 DogCatcherNet 🐶 "
"⭐ customerReviewStars: two ⭐"
"⭐ weightInPounds: 2.6 ⭐"
"Type: 🐶 Date 🐶 "
"⭐ timeIntervalSinceReferenceDate: 551150080.774974 ⭐"

This shows all the properties’ names and values. The names appear as they do in your code. For example, customerReviewStars is literally how the property name is spelled in code.

CustomReflectable

What if you wanted more of a dog and pony show in which the property names are displayed more clearly as well? What if you didn’t want some of the properties displayed? What if you wanted items displayed that are not technically part of the type? You’d use CustomReflectable.

CustomReflectable provides the hook with which you can specify what parts of a type instance are shown by using a custom Mirror. To conform to CustomReflectable, a type must define the customMirror property.

After speaking with several dog catcher programmers, you’ve discovered that spitting out the weightInPounds of the net has not helped with debugging. However, the customerReviewStars information is extremely helpful and they’d like the label for customerReviewStars to appear as “Customer Review Stars.” Now, it’s time to make DogCatcherNet conform to CustomReflectable.

Add the following code right under ☆ Add Conformance to CustomReflectable for DogCatcherNet here:

extension DogCatcherNet: CustomReflectable {
  public var customMirror: Mirror {
    return Mirror(DogCatcherNet.self,
                  children: ["Customer Review Stars": customerReviewStars,
                            ],
                  displayStyle: .class, ancestorRepresentation: .generated)
  }
}

Run the playground and see the following output:

"Type: 🐶 DogCatcherNet 🐶 "
"⭐ Customer Review Stars: two ⭐"

Where’s the Dog?
The whole point of the net is to handle having a dog. When the net is populated with a dog, there must be a way to pull out information about the dog in the net. Specifically, you need the dog’s name and age.

The playground page already has a Dog class. It’s time to connect Dog with DogCatcherNet. In the spot labeled as ☆ Add Optional called dog of type Dog here, add the following property to DogCatcherNet:

var dog: Dog?

With the dog property added to the DogCatcherNet, it’s time to add the dog to the customMirror for the DogCatcherNet. Add the following dictionary entries right after the line children: ["Customer Review Stars": customerReviewStars,:

"dog": dog ?? "",
"Dog name": dog?.name ?? "No name"

This will output the dog using its default debug description and dog’s name, respectively labeled “dog” and “Dog name.”

Time to gently put a dog into the net. Right under ☆ Uncomment assigning the dog, uncomment that line so the cute little dog is put into the net:

net.dog = Dog() // ☆ Uncomment out assigning the dog

Run the playground and see the following:

"Type: 🐶 DogCatcherNet 🐶 "
"⭐ Customer Review Stars: two ⭐"
"⭐ dog: __lldb_expr_23.Dog ⭐"
"⭐ Dog name: Abby ⭐"

Mirror Convenience
It’s pretty nice to be able to see everything. However, there are those times when you just want to pluck out a part from a mirror. To do that, you use descendant(_:_:). Add the following code to the end of the playground page to create a mirror and use descendant(_:_:) to pluck out the name and age:

let netMirror = Mirror(reflecting: net)

print ("The dog in the net is \(netMirror.descendant("dog", "name") ?? "nonexistent")")
print ("The age of the dog is \(netMirror.descendant("dog", "age") ?? "nonexistent")")

Run the playground and see at the bottom of the console output:

The dog in the net is Bernie
The age of the dog is 2

That’s doggone dynamic introspection there. It can be quite useful for debugging your own types! Having deeply explored Mirror, you’re done with DogMirror.xcplaygroundpage.

Wrapping Up Mirror and Debug Output

There are many ways to track, like a bloodhound, what’s going on in a program. CustomDebugStringConvertible, dump and Mirror let you see more clearly what you are hunting for. Swift’s introspection power is highly useful — especially as you start building bigger and more complex applications!

Key Paths

On the subject of tracking what’s going on in a program, Swift has something wonderful called key paths. For capturing an event such as when a value has changed in a third-party library object, look to KeyPath‘s observe for help.

In Swift, key paths are strongly typed paths whose types are checked at compile time. In Objective-C, they were only strings. The tutorial What’s New in Swift 4? does a great job covering the concepts in the Key-Value Coding section.

There are several different types of KeyPath. Commonly discussed types include KeyPath, WritableKeyPath and ReferenceWritableKeyPath. Here’s a summary of the different ones:

  • KeyPath: Specifies a root type on a specific value type.
  • WritableKeyPath: A KeyPath whose value you can also write to. It doesn’t work with classes.
  • ReferenceWritableKeyPath: A WritableKeyPath used for classes since classes are reference types.

A practical example of using a key path is in observing or capturing when a value changes on an object.

When you encounter a bug involving a third-party object, there is immense power in knowing when the state of that object changes. Beyond debugging, sometimes it just makes sense to hook up your custom code to respond when a value changes in a third-party object such as Apple’s UIImageView object. In Design Patterns on iOS using Swift – Part 2/2, you can learn more about the observer pattern in the section titled Key-Value Observing (KVO).

However, there’s a use case here related to the kennels that fits right into our doggy world. Without this KVO power, how would dog catchers easily know when the kennels are available to put more dogs inside? Although many dog catchers would just love to take home each and every lost dog they find, it’s just not practical.

So dog catchers who just want to help dogs find their way home need to know when the kennels are available to place dogs into. The first step to making this possible is creating a key path. Open the KennelsKeyPath page and, right after ☆ Add KeyPath here, add this:

let keyPath = \Kennels.available

This is how you create a KeyPath. You use a backslash on the type, followed by a chain of dot-separated properties — in this case, one property deep. To use the KeyPath to observe changes to the available property, add the following code after ☆ Add observe method call here:

kennels.observe(keyPath) { kennels, change in
  if (kennels.available) {
    print("kennels are available")
  }
}

Click run and see the following message output to the console:

Kennels are available.

This approach can also be great for figuring out when a value has changed. Imagine being able to debug state changes to third-party objects! Nailing down when an item of interest changes can really keep you from barking up the wrong tree.

You’re done with the KennelsKeyPath page!

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.