Swift Protocols
Protocols define a blueprint of methods and properties that any class, struct, or enum can adopt, which is the basis of Swift's protocol-oriented programming style.
Defining and Conforming to Protocols
A protocol declares requirements — properties, methods, or initializers — without providing an implementation. Any type that conforms adds its own implementation of every requirement. Structs, classes, and enums can all conform to the same protocol, which lets very different kinds of types be used interchangeably wherever the protocol is expected.
Protocols as Types
Once a protocol exists, it can be used as a type in its own right: as a function parameter, a return type, or the element type of a collection. Code written against the protocol works with any current or future conforming type, which is the essence of protocol-oriented programming.
- Property requirements specify a name, type, and whether they need { get } or { get set }.
- Methods on a protocol that mutate a value-type instance must be marked mutating in the protocol declaration.
- A protocol can require specific initializers, which conforming classes must mark required.
- Protocols can inherit from other protocols, combining and extending their requirements.
- Protocol composition with & (for example, Codable & Equatable) creates an inline type requiring conformance to several protocols at once.
A struct and a class conforming to the same protocol
protocol Greetable {
var name: String { get }
func greet() -> String
}
struct Visitor: Greetable {
var name: String
func greet() -> String { "Hello, I'm \(name), a visitor." }
}
class Employee: Greetable {
var name: String
init(name: String) { self.name = name }
func greet() -> String { "Hi, I'm \(name), staff." }
}
let people: [Greetable] = [Visitor(name: "Lee"), Employee(name: "Kim")]
for person in people {
print(person.greet())
}Protocol composition as a parameter type
protocol Identifiable {
var id: String { get }
}
protocol Named {
var name: String { get }
}
struct Product: Identifiable, Named {
var id: String
var name: String
}
func describe(_ item: Identifiable & Named) -> String {
"\(item.id): \(item.name)"
}
let product = Product(id: "P100", name: "Keyboard")
print(describe(product)) // P100: KeyboardProtocol Extensions and Default Implementations
An extension on a protocol can supply a default implementation for one of its requirements, or add entirely new methods available to every conforming type. A conforming type automatically gets the default unless it provides its own implementation, which overrides the default for that specific type.
Default implementations via a protocol extension
protocol Loggable {
var logPrefix: String { get }
func log(_ message: String)
}
extension Loggable {
var logPrefix: String { "[LOG]" }
func log(_ message: String) {
print("\(logPrefix) \(message)")
}
}
struct NetworkClient: Loggable {}
struct Database: Loggable {
var logPrefix: String { "[DB]" }
}
NetworkClient().log("Request sent") // [LOG] Request sent
Database().log("Query executed") // [DB] Query executedExercise: Swift Protocols
What does a Swift protocol define?