Skip to main content

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:

swift
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 }):

  1. Read-only properties: The conforming type must provide at least a getter for the property.
  2. 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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
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:

swift
// 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 and Podcast
  • We created a MediaPlayer class that works with any MediaItem

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).

swift
// ❌ 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:

swift
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:

swift
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:

  1. Create a Playable protocol with properties for isPlaying, volume, and currentTime. Then implement it for a VideoPlayer class.

  2. Define a BankAccount protocol with properties for balance and accountNumber. Create two conforming types: SavingsAccount and CheckingAccount with their own unique properties.

  3. Create a protocol for Theme with color properties, then implement both a LightTheme and DarkTheme. Add a protocol extension that provides computed properties for derived colors.

Additional Resources

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! :)