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 3 of 4 of this article. Click here to view the first page.

Understanding Dynamic Member Lookup

If you’ve been keeping up with Swift 4.2 changes, you may have heard about Dynamic Member Lookup. If not, you’ll go beyond just learning the concept here.

In this part of the tutorial, you’ll see the power of Dynamic Member Lookup in Swift by going over an example of how to create a real JSON DSL (Domain Specification Language) that allows the caller to use dot notation to access values from a JSON dictionary.

Dynamic Member Lookup empowers the coder to use dot syntax for properties that don’t exist at compile time as opposed to messier ways. In short, you’re coding on faith that the members will exist at runtime and getting nice-to-read code in the process.

As mentioned in the proposal for this feature and associated conversations in the Swift community, this power offers great support for interoperability with other languages such as Python, database implementors and creating boilerplate-free wrappers around “stringly-typed” APIs such as CoreImage.

Introducing @dynamicMemberLookup

Open the DogCatcher page and review the code. In the playground, Dog represents the way a dog is running with Direction.

With the dynamicMemberLookup power, directionOfMovement and moving can be accessed even though those properties don’t explicitly exist. It’s time to make Dog dynamic.

Adding dynamicMemberLookup to the Dog

The way to activate this dynamic power is through the use of the type attribute @dynamicMemberLookup.

Add the following code under ☆ Add subscript method that returns a Direction here:

subscript(dynamicMember member: String) -> Direction {
  if member == "moving" || member == "directionOfMovement" {
    // Here's where you would call the motion detection library
    // that's in another programming language such as Python
    return randomDirection()
  }
  return .motionless
}

Now add dynamicMemberLookup to Dog by uncommenting the line that’s marked ☆ Uncomment this line above Dog.

You can now access a property named directionOfMovement or moving. Give it a try by adding the following on the line after ☆ Use the dynamicMemberLookup feature for dynamicDog here:

let directionOfMove: Dog.Direction = dynamicDog.directionOfMovement
print("Dog's direction of movement is \(directionOfMove).")

let movingDirection: Dog.Direction = dynamicDog.moving
print("Dog is moving \(movingDirection).")

Run the playground. With the values sometimes being left and sometimes being right, the first two lines you should see are similar to:

Dog's direction of movement is left.
Dog is moving left.

Overloading subscript(dynamicMember:)

Swift supports overloading subscript declarations with different return types. Try this out by adding a subscript that returns an Int right under ☆ Add subscript method that returns an Int here:

subscript(dynamicMember member: String) -> Int {
  if member == "speed" {
    // Here's where you would call the motion detection library
    // that's in another programming language such as Python.
    return 12
  }
  return 0
}

Now, you can access a property named speed. Speed to victory by adding the following under movingDirection that you added earlier:

let speed: Int = dynamicDog.speed
print("Dog's speed is \(speed).")

Run the playground. The output should contain:

Dog's speed is 12.

Pretty nice, huh? That’s a powerful feature that keeps the code looking nice even if you need to access other programming languages such as Python. As hinted at earlier, there’s a catch…

Compiler and Code Completion Gone to the Dogs

In exchange for this dynamic runtime feature, you don’t get the benefits of compile-time checking of properties that depend on the subscript(dynamicMember:) functionality. Also, Xcode’s code completion feature can’t help you out either. However, the good news is that professional iOS developers read more code than they write.

The syntactic sugar that Dynamic Member Lookup gives you is nothing to just throw away. It’s a nice feature that makes certain specific use cases of Swift and language interoperability bearable and enjoyable to view.

Friendly Dog Catcher

The original proposal for Dynamic Member Lookup addressed language interoperability, particularly with Python. However, that’s not the only circumstance where it’s useful.

To demonstrate a pure Swift use case, you’re going to work on the JSONDogCatcher code found in DogCatcher.xcplaygroundpage. It’s a simple struct with a few properties designed to handle String, Int and JSON dictionary. With a struct like this, you can create a JSONDogCatcher and ultimately go foraging for specific String or Int values.

Traditional Subscript Method
A traditional way of drilling down into a JSON dictionary with a struct like this is to use a subscript method. The playground already contains a traditional subscript implementation. Accessing the String or Int values using the subscript method typically looks like the following and is also in the playground:

let json: [String: Any] = ["name": "Rover", "speed": 12,
                          "owner": ["name": "Ms. Simpson", "age": 36]]

let catcher = JSONDogCatcher.init(dictionary: json)

let messyName: String = catcher["owner"]?["name"]?.value() ?? ""
print("Owner's name extracted in a less readable way is \(messyName).")

Although you have to look past the brackets, quotes and question marks, this works.

Run the playground. You can now see the following:

Owner's name extracted in a less readable way is Ms. Simpson.

Although it works fine, it would be easier on the eyes to just use dot syntax. With Dynamic Member Lookup, you can drill down a multi-level JSON data structure.

Adding dynamicMemberLookup to the Dog Catcher
Like Dog, it’s time to add the dynamicMemberLookup attribute to the JSONDogCatcher struct.

Add the following code right under ☆ Add subscript(dynamicMember:) method that returns a JSONDogCatcher here:

subscript(dynamicMember member: String) -> JSONDogCatcher? {
  return self[member]
}

The subscript(dynamicMember:) method calls the already existing subscript method but takes away the boilerplate code of using brackets and String keys. Now, uncomment the line which has ☆ Uncomment this line above JSONDogCatcher:

@dynamicMemberLookup
struct JSONDogCatcher {

With that in place, you can use dot notation to get the dog’s speed and owner’s name. Try it out by adding the following right under ☆ Use dot notation to get the owner’s name and speed through the catcher:

let ownerName: String = catcher.owner?.name?.value() ?? ""
print("Owner's name is \(ownerName).")

let dogSpeed: Int = catcher.speed?.value() ?? 0
print("Dog's speed is \(dogSpeed).")

Run the playground. See the speed and the owner’s name in the console:

Owner's name is Ms. Simpson.
Dog's speed is 12.

Now that you have the owner’s name, the dog catcher can contact the owner and let her know her dog has been found!

What a happy ending! The dog and its owner are together again and the code looks cleaner. Through the power of dynamic Swift, this dynamic dog can go back to chasing bunnies in the backyard.

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.