Protocols, an integral part of Swift, hold a significant place in Swift development. They help us define a blueprint of methods, properties, and other requirements for particular tasks or functionalities.
As wonderful as protocols are, it's easy to make mistakes while using them, particularly when you are just starting with Swift. This post will outline some common mistakes developers make with protocols in Swift and how to avoid them. We will illustrate this with practical code examples.
1. Confusion Between Protocol and Concrete Types
One common mistake is confusing protocols with concrete types. Protocols define a blueprint, they cannot be instantiated directly. Let's see an example:
protocol Vehicle {
var numberOfWheels: Int { get }
}
struct Car: Vehicle {
var numberOfWheels: Int
}
let myVehicle: Vehicle = Vehicle() // WRONG
In the above example, we're trying to create an instance of a protocol which isn't valid. Instead, we should instantiate a struct or class that conforms to that protocol:
let myVehicle: Vehicle = Car(numberOfWheels: 4) // RIGHT
2. Not Implementing Required Properties or Methods
When a type promises to conform to a protocol, it must implement all the required properties and methods of that protocol. Forgetting to do so will lead to a compile-time error:
protocol Drawable {
func draw()
}
struct Circle: Drawable { // ERROR: Type 'Circle' does not conform to protocol 'Drawable'
}
The Circle
struct says it conforms to Drawable
, but doesn't implement the draw()
method. The correct implementation would be:
struct Circle: Drawable {
func draw() {
print("Drawing a circle.")
}
}
3. Misunderstanding of Protocol Inheritance
Protocols can inherit from other protocols, and this can sometimes lead to confusion. A common mistake is forgetting that a type conforming to a protocol that inherits from another protocol must satisfy the requirements of all protocols in its inheritance chain.
protocol Vehicle {
var numberOfWheels: Int { get }
}
protocol MotorVehicle: Vehicle {
var engineType: String { get }
}
struct Car: MotorVehicle { // ERROR: Type 'Car' does not conform to protocol 'Vehicle'
var engineType: String
}
In this example, Car
conforms to MotorVehicle
but does not provide the numberOfWheels
property required by the Vehicle
protocol. To fix this, we must provide all the required properties:
struct Car: MotorVehicle {
var numberOfWheels: Int
var engineType: String
}
4. Expecting Protocols to Store State
Swift protocols cannot store state. They can only declare properties, which must be implemented by the conforming types. This often confuses developers who come from class-based or object-oriented backgrounds.
protocol Drawable {
var color: String { get set } // Error: Property in protocol must have explicit { get } or { get set }
}
To avoid this, always remember that protocols are used to define a blueprint for methods and computed properties, not to store the state.
5. Misuse of Self
in Protocols
When defining a protocol, Self
is used to represent the current type. This can lead to problems if you try to use it in a way that doesn't make sense:
protocol Copyable {
func copy() -> Self
}
struct Note: Copyable {
var text: String
func copy() -> Note { // ERROR: Method 'copy()' in protocol 'Copyable' requires the return type to be 'Self'
return Note(text: self.text)
}
}
In the above example, we've defined a Copyable
protocol with a copy()
method that should return an instance of the conforming type. However, when we attempt to implement this in the Note
struct, we specify Note
as the return type instead of Self
. This is incorrect because Self
can refer to any type that implements the protocol (including any subclasses, in the case of classes), not just Note
. The correct implementation would be:
struct Note: Copyable {
var text: String
func copy() -> Self {
return Self(text: self.text)
}
}
6. Ignoring Associated Types in Protocols
Another common mistake when using protocols is ignoring associated types. Protocols in Swift can contain associated type placeholders, which are replaced with a real type when the protocol is adopted. Here's an example of where this goes wrong:
protocol Container {
associatedtype Item
mutating func add(_ item: Item)
var count: Int { get }
}
struct Stack: Container { // ERROR: Type 'Stack' does not conform to protocol 'Container'
var items = [Int]()
mutating func add(_ item: Int) {
items.append(item)
}
var count: Int {
return items.count
}
}
In the above example, Stack
doesn't define what Item
is, which leads to a compilation error. The correct way to implement the protocol would be:
struct Stack: Container {
typealias Item = Int
var items = [Item]()
mutating func add(_ item: Item) {
items.append(item)
}
var count: Int {
return items.count
}
}
By understanding these common mistakes and how to avoid them, you can start to harness the full power of protocols in your Swift applications. Remember, practice makes perfect. The more you use protocols, the more familiar you will become with their rules and nuances.
I hope you enjoyed this article, and if you have any questions, comments, or feedback, then feel free to comment here or reach out via Twitter.
Thanks for reading!