Protocol Composition in Swift
Sep 04, 2023In Swift, protocol composition is a powerful feature that allows you to combine multiple protocols into a single name. This can be very useful when you want to define a type that needs to adhere to multiple protocols simultaneously. Instead of writing multiple protocols again and again, you can just give a single name by combining them.
In this article, you will learn about:
- Protocol Composition without typealias
- Protocol Composition with typealias
- Using Composition in Functions
- Predefined Protocol Composition
- Where to go next?
Let's deep dive into some practical use cases of protocol composition with examples.
Protocol Composition without Typealias :
Imagine you're building a weather app, and you want to display weather information for various locations. You need to represent both the weather data and the location data, and you also want to display the weather information. You'll first define all the protocols that represent the required functionality:
// A protocol to store weather information.
protocol WeatherData {
var temperature: Double { get }
var condition: String { get }
}
// A protocol to store location information.
protocol LocationData {
var cityName: String { get }
var latitude: Double { get }
var longitude: Double { get }
}
// A protocol to display information.
protocol Displayable {
func printInfo()
}
In the code snippet, we declared three different protocols WeatherData
, LocationData
, and Displayable
.
- The
WeatherData
protocol can be used to hold the weather information. - The
LocationData
protocol can be used to hold the location information. - The
Displayable
protocol to display the weather information.
Further, create a struct called City
that conforms to all the above protocols. But how these three protocols can be relate to City type?
Well, the WeatherData
protocol can be used to define the weather information for a city. The LocationData
protocol can be used to define the location information of a city. Similarly, the Displayable
protocol can be used to print the city information.
Here is the struct City
that conforms to all protocols:
struct City: WeatherData, LocationData, Displayable {
let cityName: String
let latitude: Double
let longitude: Double
let temperature: Double
let condition: String
func printInfo() {
print("City: \(cityName), Condition: \(condition), Temperature: \(temperature)°F")
}
}
In the preceding example, the City
conforms to the protocols and implements the required method and properties. Now, whenever you need to conform all these protocols to a type, it requires to conform all protocols separately.
You can create an instance of City
and utilize its combined functionality:
let newYork = City(cityName: "New York", latitude: 40.7128, longitude: -74.0060, temperature: 75.0, condition: "Sunny")
newYork.printInfo() // Output: City: New York, Condition: Sunny, Temperature: 75.0°F
In this example, the City
struct conforms to multiple protocols:
WeatherData
, LocationData
, and Displayable
. This composition allows you to create instances that represent weather data for specific locations while also providing the ability to print the information.
Protocol Composition with Typealias :
In the previous example, we defined three protocols: WeatherData
, LocationData
, and Displayable
. We then created a struct WeatherLocation
that conformed to all three protocols. Instead of directly conforming to all three protocols, we can simplify the conformance using typealias and protocol composition.
Now, this is how we can create a new typealias for all the protocols like below:
// Protocol Composition: CityData
typealias CityData = WeatherData & LocationData & Displayable
struct City: CityData {
let cityName: String
let latitude: Double
let longitude: Double
let temperature: Double
let condition: String
func printInfo() {
print("City: \(cityName), Condition: \(condition), Temperature: \(temperature)°F")
}
}
In the example with typealias, the typealias CityData
clearly communicates the intent of the composition, making the City
struct's conformance more concise and easier to understand.
Using typealias for protocol composition can lead to cleaner, more readable, and more maintainable code, especially in cases where you need to work with multiple protocols. It enhances code organization and reduces the visual noise associated with directly listing multiple protocol conformances.
Using Protocol Composition in Functions :
Using protocol composition in functions is a powerful technique in Swift that allows you to define functions which accept parameters conforming to multiple protocols. This enhances the flexibility and reusability of your code, enabling you to work with a wider range of types that provide specific functionalities.
Let's say you have two protocols, TextMessage
and ImageMessage
, and you want to create a function displayMessage()
that works with types conforming to both protocols.
To call the function displayMessage()
, you have to provide the value of type Message
struct that conform to both protocols (TextMessage & ImageMessage):
protocol TextMessage {
var text: String { get }
}
protocol ImageMessage {
var imageURL: String { get }
}
struct Message: TextMessage & ImageMessage {
var text: String
var imageURL: String
}
func displayMessage(_ message: TextMessage & ImageMessage) {
print("Message text: \(message.text) and image URL: \(message.imageURL)")
}
let newMessage = Message(text: "Hello, Swift Anytime", imageURL: "sampleurl.com/image/image1")
displayMessage(newMessage) // Prints: Message text: Hello, Swift Anytime and image URL: sampleurl.com/image/image1
Here, the Message
instance satisfies the requirements of both TextMessage and ImageMessage, making them suitable arguments for the displayMessage()
function.
By using protocol composition in functions, you create functions that can work with a broad range of types.
Overusing composition can lead to overly complex and less readable code. Make sure that protocol composition enhances your code's structure and reusability rather than complicating it unnecessarily.
Predefined Protocol Composition :
In the Swift library, there are many protocol compositions that are predefined to conform to multiple protocols. For example,
In Swift, Codable
is a form of protocol composition. The Codable protocol is actually a typealias for a composition of two protocols: Encodable
and Decodable
. These two protocols work together to provide a unified way to encode and decode data, making it easy to serialize and deserialize objects to and from various formats such as JSON.
Here's how the Codable protocol is defined:
public typealias Codable = Encodable & Decodable
- Encodable defines the protocol for types that can be encoded (serialized) into an external representation, such as JSON or binary data.
- Decodable defines the protocol for types that can be decoded (deserialized) from an external representation back into a Swift object.
When you conform to the Codable
protocol, you're effectively conforming to both Encodable
and Decodable
, which allows you to perform encoding and decoding operations on instances of that type without needing to explicitly implement the encoding and decoding methods.
Here's an example of using the Codable protocol:
struct Person: Codable {
var name: String
var age: Int
}
In this example, Person
conforms to the Codable
protocol, allowing us to easily encode it to JSON and decode it back from JSON using the JSONEncoder
and JSONDecoder
classes.
This shows how Codable
combines the functionalities of Encodable
and Decodable
in a composition, making it easier to work with data serialization and deserialization in Swift.
Where to go next?
Congratulations, you are now one step ahead in the journey to master Protocol Oriented Programming concepts in Swift. In order to continue your learnings, we recommend you check out Protocol Extensions in Swift and Guide to Protocol Oriented Programming.