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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Dynamic Features in Swift
25 mins
- Getting Started
- Reflecting on Mirror and Debug Output
- CustomDebugStringConvertible
- Dump
- Swift Mirror
- Creating a Mirror-Powered Dog Log
- Creating a Mirror
- CustomReflectable
- Wrapping Up Mirror and Debug Output
- Key Paths
- Understanding Dynamic Member Lookup
- Introducing @dynamicMemberLookup
- Adding dynamicMemberLookup to the Dog
- Overloading subscript(dynamicMember:)
- Compiler and Code Completion Gone to the Dogs
- Friendly Dog Catcher
- Where to Go From Here?
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.