Swift Protocol Properties
When designing interfaces in Swift, protocols allow you to define property requirements that conforming types must implement. Understanding how to work with properties in protocols is essential for creating flexible, reusable code. In this guide, we'll explore how property requirements work in Swift protocols and how to implement them effectively.
Introduction to Protocol Properties
In Swift, protocols can define property requirements that any conforming type must satisfy. These properties can be either read-only or read-write, but protocols don't specify whether the property should be implemented as a stored property or a computed property—they only define the property name, type, and accessibility.
Let's start with the basics of how to declare property requirements in protocols:
protocol Vehicle {
// Read-write property (must be gettable and settable)
var speed: Double { get set }
// Read-only property (must be at least gettable)
var model: String { get }
}
In the above protocol, any type that conforms to Vehicle
must provide:
- A
speed
property that can be both read and modified - A
model
property that can at least be read
Property Requirements in Protocols
Read-only vs Read-write Properties
When defining properties in protocols, you specify whether they should be read-only ({ get }
) or read-write ({ get set }
):
- Read-only properties: The conforming type must provide at least a getter for the property.
- Read-write properties: The conforming type must provide both a getter and setter for the property.
Let's see how a class might conform to our Vehicle
protocol:
class Car: Vehicle {
// Stored property implementation of speed
var speed: Double = 0.0
// Computed property implementation of model
var model: String {
return "Tesla Model 3"
}
}
// Let's use our Car
let myCar = Car()
myCar.speed = 60.0
print(myCar.speed) // Output: 60.0
print(myCar.model) // Output: Tesla Model 3
Static and Class Properties
Protocols can also define static or class property requirements:
protocol VehicleFactory {
// Static read-only property
static var defaultWheelCount: Int { get }
// Static read-write property
static var availableModels: [String] { get set }
}
struct BikeFactory: VehicleFactory {
// Implement the static properties
static var defaultWheelCount: Int = 2
static var availableModels: [String] = ["Mountain", "Road", "Hybrid"]
}
print(BikeFactory.defaultWheelCount) // Output: 2
BikeFactory.availableModels.append("Electric")
print(BikeFactory.availableModels) // Output: ["Mountain", "Road", "Hybrid", "Electric"]
Implementation Options for Protocol Properties
When conforming to a protocol, you have several options for implementing the required properties:
1. Stored Properties
The simplest implementation is using stored properties:
protocol Person {
var name: String { get }
var age: Int { get set }
}
class Student: Person {
// Stored property for name (constant)
let name: String
// Stored property for age (variable)
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let student = Student(name: "Alex", age: 21)
print("\(student.name) is \(student.age) years old")
// Output: Alex is 21 years old
2. Computed Properties
You can also use computed properties to satisfy protocol requirements:
struct Circle {
var radius: Double
}
protocol Shape {
var area: Double { get }
}
extension Circle: Shape {
// Computed property to calculate area
var area: Double {
return Double.pi * radius * radius
}
}
let circle = Circle(radius: 5)
print("Circle area: \(circle.area)")
// Output: Circle area: 78.53981633974483
3. Property Observers
For read-write protocol properties, you can even implement property observers:
protocol Progress {
var percentage: Double { get set }
}
class Download: Progress {
var percentage: Double = 0 {
willSet {
print("About to update progress to \(newValue)%")
}
didSet {
if percentage >= 100 {
print("Download completed!")
}
}
}
}
let download = Download()
download.percentage = 50
// Output: About to update progress to 50%
download.percentage = 100
// Output:
// About to update progress to 100%
// Download completed!
Protocol Extensions and Default Implementations
Swift allows you to provide default implementations for properties in protocol extensions:
protocol Identifiable {
var id: String { get }
var displayName: String { get }
}
extension Identifiable {
// Default implementation for displayName
var displayName: String {
return "ID: \(id)"
}
}
struct User: Identifiable {
var id: String
// No need to implement displayName as it uses the default
}
let user = User(id: "user123")
print(user.displayName) // Output: ID: user123
However, it's important to note that you can only provide default implementations for computed properties, not stored properties, as protocol extensions cannot add stored properties to types.
Read-only in Protocol, Read-write in Implementation
An important aspect to understand is that if a protocol requires a property to be read-only ({ get }
), the conforming type can still implement it as a read-write property:
protocol Animal {
var species: String { get } // Protocol requires read-only
}
struct Dog: Animal {
var species: String // Implemented as read-write (both get and set)
}
var dog = Dog(species: "Canis familiaris")
dog.species = "Canine" // This is allowed because the implementation is read-write
print(dog.species) // Output: Canine
Practical Example: Building a Media Player
Let's look at a more comprehensive example that demonstrates protocol properties in a real-world scenario:
// Define a protocol for media items
protocol MediaItem {
var title: String { get }
var duration: Double { get }
var isPlaying: Bool { get set }
}
// Add computed properties through extension
extension MediaItem {
var formattedDuration: String {
let minutes = Int(duration) / 60
let seconds = Int(duration) % 60
return "\(minutes)m \(seconds)s"
}
}
// Song implementation
struct Song: MediaItem {
let title: String
let artist: String
let duration: Double
var isPlaying: Bool = false
mutating func play() {
isPlaying = true
print("Now playing: \(title) by \(artist)")
}
}
// Podcast implementation
struct Podcast: MediaItem {
let title: String
let host: String
let duration: Double
var isPlaying: Bool = false
var currentTime: Double = 0
mutating func play() {
isPlaying = true
print("Now playing podcast: \(title) hosted by \(host)")
}
}
// Media player that works with any MediaItem
class MediaPlayer {
var playlist: [MediaItem] = []
func addToPlaylist(item: MediaItem) {
playlist.append(item)
}
func displayPlaylist() {
for (index, item) in playlist.enumerated() {
let playingStatus = item.isPlaying ? "▶️" : "⏸️"
print("\(index + 1). \(playingStatus) \(item.title) (\(item.formattedDuration))")
}
}
}
// Create media items
var song = Song(title: "Shake It Off", artist: "Taylor Swift", duration: 219)
var podcast = Podcast(title: "Swift by Sundell", host: "John Sundell", duration: 1800)
// Add to player and display
let player = MediaPlayer()
player.addToPlaylist(item: song)
player.addToPlaylist(item: podcast)
song.play()
player.displayPlaylist()
/* Output:
Now playing: Shake It Off by Taylor Swift
1. ▶️ Shake It Off (3m 39s)
2. ⏸️ Swift by Sundell (30m 0s)
*/
In this example:
- We defined a
MediaItem
protocol with property requirements - We provided a computed property through a protocol extension
- We implemented the protocol in two different types:
Song
andPodcast
- We created a
MediaPlayer
class that works with anyMediaItem
Common Pitfalls and Best Practices
1. Cannot Provide Default Values
Protocols cannot provide default values for properties. This must be done in the conforming type or through protocol extensions (for computed properties only).
// ❌ This won't work
protocol MyProtocol {
var myProperty: Int = 10 { get set } // Error: Protocols cannot provide default values
}
// ✅ This is the correct approach
protocol MyProtocol {
var myProperty: Int { get set }
}
// Default implementation can be provided through extension (computed properties only)
extension MyProtocol {
var derivedProperty: Int {
return 10
}
}
2. Type Property vs. Instance Property
Be clear about whether you're requiring a type property (static
) or an instance property:
protocol Configurable {
// Type property
static var defaultConfiguration: [String: Any] { get }
// Instance property
var currentConfiguration: [String: Any] { get set }
}
3. Use Protocol Composition for Complex Requirements
If you need a type to conform to multiple protocols with property requirements, use protocol composition:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
func greet(person: Named & Aged) {
print("Hello \(person.name), you are \(person.age) years old!")
}
struct Employee: Named, Aged {
var name: String
var age: Int
}
let employee = Employee(name: "Bob", age: 35)
greet(person: employee) // Output: Hello Bob, you are 35 years old!
Summary
Protocol properties are a powerful feature in Swift that allow you to define contracts for the types that conform to your protocols. Key points to remember:
- Protocols can require properties that are read-only (
{ get }
) or read-write ({ get set }
) - Conforming types can implement required properties as stored or computed properties
- Protocol extensions can provide default implementations for computed properties
- Properties required as read-only in the protocol can be implemented as read-write in conforming types
- Protocol properties can be used to create flexible, reusable interfaces
By mastering protocol properties, you can design more modular, flexible code that leverages Swift's powerful type system.
Exercises
To reinforce your understanding of protocol properties, try these exercises:
-
Create a
Playable
protocol with properties forisPlaying
,volume
, andcurrentTime
. Then implement it for aVideoPlayer
class. -
Define a
BankAccount
protocol with properties forbalance
andaccountNumber
. Create two conforming types:SavingsAccount
andCheckingAccount
with their own unique properties. -
Create a protocol for
Theme
with color properties, then implement both aLightTheme
andDarkTheme
. Add a protocol extension that provides computed properties for derived colors.
Additional Resources
- Swift Documentation on Protocols
- WWDC Sessions on Protocol-Oriented Programming
- Swift by Sundell: Computed Properties
Happy coding with Swift protocols!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)