Hallo allemaal, Leo hier. Today we will talk about a not-so-known Swift language topic which is Swift Metatypes.

Have you ever declared a variable or constant in Swift and wondered what the .self or .Type keywords were all about?

If so, you’ve stumbled upon the concept of metatypes in Swift. But what exactly are metatypes and why should you care about them? In this article, we’ll explore what metatypes are, how they work, and why they’re an important part of the Swift language.

I’ll try to simplify this topic to the extreme, this way everyone can understand it. Also, there was a long time without writing about Swift language itself, and it is a topic that I really like.

Don’t get me wrong, hacking with SwiftUI is cool, however, sometimes I like to go back to pure Swift vanilla articles.

Let’s code! But first…  

 

Painting of the Day

The painting I chose today is called The Comic Spirit, a 1927 painting by the master Rene Magritte.

Magritte started taking drawing lessons at ten years old and in 1916, he went to study at the Royal Academy of Fine Arts in Brussels. But he wasn’t a fan of the teaching there and it wasn’t his cup of tea. After serving in the Belgian infantry for a bit and working as a draftsman for a wallpaper company, he finally started his painting career. He was able to focus on painting full-time thanks to a short contract with Galerie le Centaure, but his first exhibition wasn’t well received.

To make ends meet, Magritte ran an advertising poster business with his bro and even made fake paintings by Picasso, Braque, and Chirico. He also used his skills to make fake bank notes during the German occupation of Belgium during World War II. This helped him survive during the tough economic times.

I chose this painting because it looks like a prototype of a person and we are going to study metatypes, the types that represent types, curious… isn’t it?

 

The Problem – How to inject Protocols That Only Have Static Functions?

You need to inject a protocol that only has static functions into a object.

Before diving into this problem, let’s get to know better the two stars of this day .self or .Type keywords. First, let’s try to visualize the difference between types and metatypes in Swift. 

 

Difference Between Metatype and Type in Swift

learning swift metatypes

Have you ever wanted to describe the type of value or expression in your code?

Or create an instance of a type, or return the type itself from a method? Well, that’s where metatypes come in. In Swift, a metatype is a special type that represents the type of another value or expression.

It’s like a blueprint for a type, allowing you to describe and manipulate the type itself, rather than an instance of that type. When you are dealing with types you have instances of that type.

For example:

let name: String = "Pepijn"

In the example above the constant ‘name’ is of the type String and is pointing to an instance of String that is of value “Pepijn”.

But what if I want to create a constant that points to the type String itself? That’s when metatypes come to the rescue.

Check the example below:

let metaType: String.Type = String.self

Now we have something different. The constant called ‘metaType’ is the MetaType type of String that has an instance of the Metatype Value of String.

Let me draw my mental model for you: Drawing meta types in Swift. How to create and use meta types in Swift? Tutorial step-by-step. This is my mental model for metatypes types. The relation they have with the metatype values is the same as the types have with the instances of that type. I hope that also helps you to understand how “metatypes types” relate to “metatype values”.

 

When to use .Type Keyword in Swift

You can use the .Type keyword in Swift when you want to describe the type of a value or expression, create an instance of a type, or return the type itself from a method. Some common use cases for .Type include:

  • Creating factory methods
  • Describing the type of a value or expression
  • Initializing instances of a type
  • Accessing the type of an object

 

It’s important to note that while .Type is a useful tool, it should be used judiciously and only when necessary.

Overusing .Type can make your code harder to read and understand, so be sure to use it sparingly and only in situations where it provides real value.  

 

Understanding the .Type Keyword in Swift

The Type keyword in Swift refers to the type of a value or expression. It can be used to get the type of a value, for instance:

let name = "Ninha"
print(type(of: name)) // Output: String

With the type(of:) function you can discover what is the underlying type of any object in Swift. Look it in the playgrounds:

Swift Metatype tutorials. How to use Swift Metatypes in your daily code.

That means… The metatype is also a type in Swift. 🤯 So we can do whatever we can do with the String.

You can also use the Type keyword or reference to creating an instance of a type, because it is the Metatype. If the return is a type, I can put that into a variable and use it usually, right? Yes! Look at the example below:

Metatype being used directly in Swift. Tutorial about Swift Metatype step-by-step

In this example, the String.self expression returns the type of String, which can then be used to create an instance of a string using the init method.  

 

Real-World Scenario Using Metatypes in Swift

Imagine that you have an object, in our case DataLoader, that uses a static function of another object.

Check the code below:

struct Logger {
    static func log(_ data: String) {
        print(data)
    }
}

struct DataLoader {
    func loadData() -> String {
        let data = "Data"
        
        Logger.log(data) 
        
        return data
    }
}

How can I unit test if I’m sending the right information to the Logger? The first thing you could think is “We just need to inject Logger into the DataLoader… Right?”.

Unfortunately, we can’t because the log function is a static function, so it is ONLY AVAILABLE at the type level (aka metatype).

Digging deeper into this… You might not know but when we use:

Logger.log(data)

Swift applies a syntax sugar for us because the code above is the same as using:

Logger.self.log(data)

I think now you are getting where we heading.

The .self keyword is getting the Logger MetaType Value this way we can access the static function. 

The explanation of metatypes starts to make sense to you now. To be able to inject the Logger type, we can use Swift metatypes to do that! We learned today that we can have constants and variables that point at metatypes.

And that’s exactly what we are going to do: First, we will create a protocol to generalize the logger:

protocol Loggable {
    static func log(_ data: String)
}

struct Logger: Loggable {
    static func log(_ data: String) {
        print(data)
    }
}

Now we can use our new Swift skills to inject the Loggable type into the DataLoader and enable this way the unit tests to assert if what we are sending to the logger is the right message, or even if we are calling the logger at all.

Disclaimer: if you want to learn how to insert logging into your view models and services without changing them, you could use what we discussed here.

And now to finish everything we just need to add an initializer to the struct that receives a loggable metatype value.

Observe the final result:

protocol Loggable {
    static func log(_ data: String)
}

struct Logger: Loggable {
    static func log(_ data: String) {
        print(data)
    }
}

struct DataLoader {
    let logger: Loggable.Type
    
    init(logger: Loggable.Type = Logger.self) {
        self.logger = logger
    }
    
    func loadData() -> String {
        let data = "Data"
        
        logger.log(data)
        
        return data
    }
}

let dataLoader = DataLoader() // we could inject here when we needed 
dataLoader.loadData()

See that now we have the initializer receiving a metatype value of Logger and the type of that is the metatype of type Logable.

Really cool right?

We are ready now to unit test this struct if we want. We can just create a mock/spy of the logger in unit tests and pass it in the DataLoader initializer.

And that’s how we solve the question of the article.  

 

The Most Common Example of Swift Metatypes

The most common use case for using Swift metatypes is something that we all iOS developers have done at least one time in our lives.

When you are learning about network calls and the response is coming back in form of a JSON, but before we could read the decodable object we need to decode it, right?

The famous JSONDecoder.decode function uses what we learned today because you have the initializer(init(from: Decoder) throws) in the Decodable type.

Check this:

try? JSONDecoder().decode(String.self, from: "ninha".data(using: .utf8)!)

decodable in Swift receiving a meta type value in the initializer. Tutorial why is the JSONDecoder passing the self in the function parameter

Give a closer look and you will see the decode<T>(_ type: T.Type, … ) in the function parameters. That first parameter called “_ type: T.Type” is exactly what we learned today with metatypes.

It receives any metatype value that conforms to the protocol Codable and we learned that if a function or initializer needs “something.Type” the way to get that is to use anything that is the same type of that using .self.  

 

Wrap up – Swift Metatypes and Types

In conclusion, Swift Metatypes and Types are essential components of the Swift programming language. Understanding how to use them effectively can greatly enhance your ability to write better, more efficient code.

Metatypes, specifically, allow for runtime introspection and manipulation of class and struct types, while Types provide a way to describe the type of a value.

By combining these concepts with other powerful features of Swift, developers can create flexible, dynamic, and scalable applications that can adapt to changing requirements. In short, mastering Swift Metatypes and Types is a valuable investment for any Swift programmer looking to elevate their skills.

Today we saw various examples of what metatypes are and how to use them in Swift, and that they can be very useful for your daily work. For example, using them to inject dependencies in your types or when building a generic API for networking.  

 

Summary – Swift

Metatypes are a handy tool in Swift that allows you to describe the type of a value or expression and create instances of a type. Whether you’re new to Swift or a seasoned developer, understanding metatypes is a crucial part of writing good, robust code.

By using metatypes, you’ll be able to write code that’s more expressive and intuitive, making it easier to maintain and extend.

Fellow iOS Developers, that’s all. I hope you liked reading this article as much as I enjoyed writing it.

If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page. You can likewise sponsor this blog so I can get my blog free of ad networks.

Thanks for the reading and…

That’s all folks.

Image credit: Featured Painting