A protocol defines a “blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality”. They can give also give nominal types polymorphic behavior.
In Swift, the “protocol can then be adopted by a class, structure [a struct], or enumeration [an enum] to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol” (Ref#: G).
Expanding on this further, a protocol is essentially a list of method declarations and property requirements. If you want a class to adopt a protocol, you implement those methods in your class, or you give your class any properties that are required.
Swift Protocol Syntax
The syntax for creating protocols in Swift is really straight forward:
1 2 3 |
protocol myProtocol { // Definition goes here } |
If we’re using this protocol with a structure, a struct it’s going to look like this:
1 2 3 |
struct someStruct: MyProtocol { // The struct definition goes here } |
And similarly, when we want to use a protocol in a class it looks like:
1 2 3 |
class MyClass: SomeSuperclass, MyProtocol { // class definition goes here } |
Objective-C Protocol Syntax
A protocol in Objective-C will look like this:
1 2 3 4 5 |
@protocol XYZPieChartViewDataSource -(NSUInteger)numberOfSegments; -(CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex; -(NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex; @end |
To adopt a protocol, add it to your class header file like:
1 |
@interface FavoritesViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> |
The protocol names appear after the class declaration, inside angled brackets. When adopting more than one protocol, list them in a comma-separated list.
Then in your implementation (.m) file, implement all of the required methods for each protocol. (For Cocoa classes, consult the documentation to see which methods are required and which are optional.)
Optional Protocol Methods
So from the stage of Objective-C 2.0 and later, and indeed in Swift, some protocol methods can be marked as optional. This means you don’t have to implement those, but you still have to implement all of the required methods. As we mentioned above, when you do this your class is said to conform to the protocol.
Note that when using in Swift, when we want an optional method, we do need to tap into some Objective-C by using the @objc attribute as a prefix of the optional method in the Swift protocol:
1 2 3 4 5 |
@objc protocol DetailViewControllerDelegate: AnyObject { // Thus: optional func shouldUpdateTask(sender: DetailViewController) -> Bool func didUpdateTask(sender: DetailViewController) } |
However, we then have the advantage of being able to check for a function’s existence when using it by taking advantage of optional-chaining, like this:
1 |
delegate?.shouldUpdateTask?(self) |
Meaning that the method will only be tried if it is available in the class that is conforming to the protocol.
Class Only Protocols (Swift)
AnyObject
refers to any instance of a class and is equivalent to id
in Objective-C. It’s useful when you specifically want to work with a reference type because it won’t allow any of Swift’s structs or enums to be used. AnyObject
is also used when you want to restrict a protocol so that it can be used only with classes. As of Swift 4. protocol P : class {}
and protocol P : AnyObject {}
parse identically and there’s no semantic difference between them, whereas previously there had been some subtle differences, this means that the AnyObject syntax is now preferred
1 2 3 |
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol { // class-only protocol definition goes here } |
Protocols in the Cocoa Framework
Protocols are used a lot in iPhone development and in the Cocoa framework. A really good example is that a UITableView requires a data source and a delegate object, and these must conform to the UITableViewDataSource and UITableViewDelegate protocols.
Property Requirements (Swift)
In Swift – property requirements look like:
1 2 3 4 5 6 7 |
protocol MyFunProtocol { var completed: Bool {get set} var message: String {get} // static properties are owned by the type and shared // by all instances of that type static var typeProperty: String {get} } |
Note that we must specify whether the property should be read-only or read-write in nature and the way we do this is using the get
and set
keywords.
Method Requirements (Swift)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
protocol Place { var placeName: String { get set } } protocol Describable { // A method requirement func getTheDescription() -> String } // Conformed to like class Disco: Place, Describable { var placeName: String = "Disco" func getTheDescription() -> String { return "A place to have a good time dancing." } } |
Initializer Requirements (Swift)
“Protocol can require specific initializers to be implemented by conforming types”(Ref: #F).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
protocol SomeProtocol { init(specialString: String) } // This may be implemented in conforming classes as either a convenience initializer or a designated initializer class SomeClass: SomeProtocol { var mySpecialString: String required init(specialString: String) { // initializer implementation goes here } self.mySpecialString = specialString } } let myClass = SomeClass(specialString: "YOLO") print(myClass.mySpecialString) // "YOLO" |
See more information on designated vs convenience initializers in Swift see my article here: https://www.steveclarkapps.com/initializers/
Swift Protocols Are Different
Swift protocols are a lot different from good old Objective-C protocols – Swift Protocols support:
- Protocol Inheritance
- Protocol Extensions
- Default Implementation
- Associated Types (Generics)
- Conformed to by Structs and Enums (value types) as well as Classes (reference types).
In WWDC 2015 Swift was described as being a “Protocol Oriented Language”.
Protocol Inheritance (Swift)
“A protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas” (Ref#: F).
1 2 3 4 |
// We can have multiple inherited protocols, separated by commas protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // protocol definition goes here } |
Protocol Extensions (Swift)
“Protocols can be extended to provide method, initializer, subscript, and computed property implementations to conforming types. This allows you to define behavior on protocols themselves, rather than in each type’s individual conformance or in a global function” (Ref#: F).
“When you define a protocol extension, you can specify constraints that conforming types must satisfy before the methods and properties of the extension are available. You write these constraints after the name of the protocol you’re extending by writing a generic where
clause.”(Ref#: F).
1 2 3 4 5 6 7 8 9 10 |
extension Collection where Element: Equatable { func allEqual() -> Bool { for element in self { if element != self.first { return false } } return true } } |
Default Implementation (Swift)
“You can use protocol extensions to provide a default implementation to any method or computed property requirement of that protocol. If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension”(Ref#: F).
For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
protocol NetworkingEngine { func downloadHotels(url: URL, analytics: AnalyticsEngine, decoder: JSONDecoder, completion: @escaping (Result<Hotels, NetworkError>) -> Void) } // Allow for default parameters when conforming to protocol extension NetworkingEngine { func downloadHotels(url: URL, decoder: AnalyticsEngine = Analytics(), completion: @escaping (Result<Hotels, NetworkError>) -> Void) { downloadHotels(url: url, analytics: analytics, decoder: decoder, completion: completion) } } |
Associated Types (Swift)
An associatedtype
is a placeholder for an unknown Concrete Type
in a swift protocol which will require concretization on adoption at Compile time, and as such is seen as an example of Generics. In the words of Paul Hudson, “they mark holes in protocols that must be filled by whatever types conform to those protocols”.
Here’s an example:
1 2 3 4 |
protocol Screen { associatedtype ItemType: Item var items: [ItemType] { get set } } |
“Associated types are effectively holes in a protocol, so when you make a type conform to this protocol it must specify what ItemType
actually means”.
( https://www.hackingwithswift.com/articles/74/understanding-protocol-associated-types-and-their-constraints )
Benefits of Using Associated Types
“This concept first appeared in a publication from “The Journal of Functional Programming” titled: extended comparative study of language support for generic programming. Where they laid emphasis on Multi-Type Concepts which is the root of Swift’s protocol associatedtypes
. Swift also drew some inspiration from Scala’s Traits and Abstract types, Haskell’s Multi-parameter type classes and from Rust Associated Types. It then leveraged the Multi-Type Concept within the standard library for [its] collection types”(Ref#: W).
Associated Types help specify the precise and exact Type
of an object within a protocol sub-typing without polluting the Type
definition.
“associated types allow the adopter of a protocol to provide multiple concrete types at compile-time, without polluting the type definition with a bunch of type parameters. They’re an interesting solution to the problem and a different kind of abstraction (abstract members) from generic type parameters (parameterization)”(Ref#: X).
N.B. associated types do differ from generic type parameters, although both have similar aims.
Protocol Oriented Programming (POP) in Swift
Many of us have heard the term Protocol Oriented Programming banded about in the land of Swift programming. However, everywhere we look when researching the topic, we struggle to come up with a single phrase that actually defines what POP is (which can lead to some confusion).
Let’s try our best here to come up with such a phrase. By “protocol” those who talk about POP are meaning very specifically Swift protocols (which are perhaps more similar to C++ abstract classes then Objective-C protocols); these are protocols which can be conformed to by value types like Structs and Enums which are “first-class citizens” in Swift, as well as reference types like normal classes. It turns out that POP is mostly OOP but with a different emphasis; the emphasis on composition over inheritance.
So a phrase that I have come up with to explain what I believe Apple means by POP is “A version of OOP which emphasizes composition over inheritance as a method of functionality-sharing (by using Swift’s version of Protocols)“.
In Swift, our value types like Structs and Enums can have properties, methods, and extensions. POP is a pattern that encourages us to use flat, non-nested structures in our code, whereas inheritance is conversely not a flat way of structuring things.
Use of Value vs Reference Types
Swift classes have reference semantics (passed by reference), but Swift also supports enums and structs that are passed by value, and these enums and structs are also able to support many of the features provided by classes. By using POP, we are steering away from class inheritance and instead favoring the use of value types because:
- When we subclass from some superclass we have to inherit all its properties and methods, some of which we may not need or want. This can mean our object getting bloated with a lot of unnecessary code.
- With a deeply nested structure of inheritance, it can be really hard to know where a problem is coming from as we need to jump up and down the hierarchy in order to find and fix bugs.
- Because inheritance uses classes which are reference types we can get unforeseen consequences from modifying a property in an instance of a class, such that we may end up with spaghetti code scenarios.
- A goal of ours should be to separate the public interface from the private implementation where we can (see SOLID principles).
- We want our software to be defined in components that talk to each other using interfaces.
- We want reusability, extensibility, black-boxed functionality, in order to make our code a hell of a lot more maintainable going forward.
- We want to make our code more testable with unit tests, and we want to be able to inject our dependencies using dependency injection, which is all facilitated by a more modular approach.
(Sources Ref#: Q, Ref#: U).
Why would we go for a protocol-oriented approach in our code? …and is there a way we can use protocols (and value types) to make our code better?
Composition over Inheritance (also known as the composite reuse principle) is an OOP principle about achieving polymorphic behavior and code re-use via composition (typically through containing instances of other classes that implement the desired functionality – but in POP via protocol adoption) rather than inheritance from a base class (as elucidated in the famous GoF Design Patterns book).
The benefits of Composition over Inheritance include: “It is more natural to build business-domain classes out of various components than trying to find commonality between them and creating a family tree”.
Composition is also less prone to the quirks of the family members because it’s better to compose what an object can do than extend what it is. The reason this is better is that it more easily accommodates future requirements changes that would otherwise require a complete restructuring of business-domain classes in the inheritance model. It means that making small changes in a part of an inheritance model need not potentially cause a raft of unforeseen consequences down the chain of inheritance (Ref#: P).
What Composition Can Look Like
Obviously, what we are doing with composition is allowing our object to conform to one or more protocols. For example, a tableview cell might be designated as Completable
where Completable is a protocol that requires a variable complete
that tells us if a form cell is complete. But this cell could also be made to conform to any number of other protocols, building up an idea of how we can reasonably treat the cell given that we know that it conforms to a given set of protocols.
1 2 3 |
struct SomeStruct: ProtocolAlpha, ProtocolBeta, ProtocolC { // Implementation } |
This will let out types adopt multiple protocols which can prove a big advantage as is get rid of the need to use a class hierarchy. We can take our requirements and break them down into smaller components, avoiding bloated types that may contain requirements that are not really needed by all the types that inherit from them.
Using frameworks like GRDB gives us examples like (allowing us to use the same struct to both parse API data, as well as save data to a local SQLite database):
1 2 3 4 5 |
import GRDB struct UserCheck: Codable, FetchableRecord, PersistableRecord { // content } |
Another Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct MyEntity: Entity, Equatable, CustomStringConvertible { var name: String // Equatable public static func ==(lhs: MyEntity, rhs: MyEntity) -> Bool { return lhs.name == rhs.name } // CustomStringConvertible public var description: String { return "MyEntity: \(name)" } } let entity1 = MyEntity(name: "42") print(entity1) let entity2 = MyEntity(name: "42") assert(entity1 == entity2, "Entities shall be equal") |
Source: https://www.pluralsight.com/guides/protocol-oriented-programming-in-swift
Default Behaviors using Protocol Extensions
Protocols can have extensions: in Swift, you can actually add default functions and properties on protocols by Extending protocols with default behaviors.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
protocol Electric { mutating func recharge() // percentage of the battery level, 0-100%. var batteryLevel: Int { get set } } // We can make the protocol have default values via extensions extension Electric { mutating func recharge() { print("Recharging the battery...") batteryLevel = 100 } } |
(Source: U)
1 2 3 4 5 6 7 8 9 10 |
// Another basic example protocol Configurable { func configurationFileName() -> String } extension Configurable { func configurationFileName() -> String { return "default.config" } } |
(Source: V)
What is “Local Reasoning”?
In the last few years at Apple’s WWDC conference (known in the community as dub dub) there have been several talks about moving towards a Protocol Oriented approach to programming (particularly in Swift). In one such talk the concept of “Local Reasoning” was covered.
Local Reasoning means that a programmer reading a piece of code can make sense of that code without having to go on a journey around the wider codebase to find out what’s actually going on. It more broadly means that you can reason about a software unit (like a module, or function) as a separate thing.
So one benefit of this POP approach that Apple is now advocating is said to be that this local reasoning is easier to do, potentially making it faster to improve or modify features of our app in our Swift code.
Protocols as Types
Although protocols don’t contain business logic, they are still considered fully-fledged types in Swift and therefore they can be used (to a large extent) like any other type. This means that we may use protocols both as parameters and return types for our functions. They may also be used to define the type of vars, constants, and objects like arrays (Ref#: T).
Use of Protocols in the Delegate Pattern
In Swift, when we want to use a delegate pattern, we typically do this by creating a protocol that defines the responsibilities of a delegate. In this context, any type that conforms to this protocol is called the delegate and it will adopt this protocol ensuring that it will provide the required functionality that has been specified in the protocol. We obviously see this pattern used very extensively in Cocoa, and most typically in things like the UITableViewDelegate and UITableViewDatasource. But, more powerfully, we can create our own protocols such as when we want some cell to return some detailed information back to the context from which it was generated in some specific format.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
protocol BoardGame { var board: Board { get } func play() } // A class-only or class-bound protocol is set by inheritance from AnyObject protocol BoardGameDelegate: AnyObject { func gameDidStart(_ game: BoardGame) func game(_ game: BoardGame, didStartNewTurnWithAvitar avitar: Avitar) func gameDidEnd(_ game: BoardGame) } // BoardGame is Conformed to by class MagicGame: BoardGame { weak var delegate: BoardGameDelegate? // Remember this must be marked "weak" func play() { print("Game play did start") delegate?.gameDidStart(self) } // Etc ... } // BoardGameDelegate is conformed to by class BoardGameTracker: BoardGameDelegate { func gameDidStart(_ game: BoardGame) { // Content } // ETC ... other required methods in delegate go here } // Run code like let tracker = BoardGameTracker() let game = MagicGame() game.delegate = tracker game.play() // SOURCE: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html |
Conclusion
In this article, we have covered the fact that a protocol (in iOS programming) is a “blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality”.
We have covered how we define a protocol in Objective-C and Swift, and how Swift protocols can end up being used with both classes and struct, if not restricted by the use of the class
or AnyObject
conformance such that they may then only be conformed to be classes.
We discovered that Protocol-Oriented Programming is an Apple buzz term, which, as I have defined it, means something like “A version of OOP which emphasizes composition over inheritance as a method of functionality-sharing by using Swift’s version of Protocols”. We also found out that protocols can help with what Apple calls “local reasoning” or the ability to see what a piece of code is doing just from looking at that scope in code, and without having to search to the codebase to establish this.
We found that Swift Protocols and POP aim to provide us with all the benefits of composition over inheritance for a flatter, more transparent and readable code structure, as opposed to a system based on inheritance where we might have a lot of inherited stuff in our classes that we don’t actually need or use.
References
A: https://www.youtube.com/watch?v=ekYdBcl3dzs&feature=push-u&attr_tag=WCN9zigcfLq3t6Rk-6
B: For Obj-C: http://www.idev101.com/code/Objective-C/protocols.html
C: For Swift: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267
D: https://www.youtube.com/watch?v=2CMsgER7WG4 (Retroactive Modeling with POP)
E: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithProtocols/WorkingwithProtocols.html
F: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
G: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
H: “https://medium.com/@abhimuralidharan/all-about-protocols-in-swift-11a72d6ea354”
I: https://www.raywenderlich.com/148448/introducing-protocol-oriented-programming
J: https://developer.apple.com/videos/play/wwdc2015/408/
K: https://developer.apple.com/videos/play/wwdc2016/419
L: “https://www.youtube.com/watch?v=ekYdBcl3dzs”
M: https://www.youtube.com/watch?v=g2LwFZatfTI
N: https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3
O: https://www.appcoda.com/protocol-oriented-programming/
P: https://en.wikipedia.org/wiki/Composition_over_inheritance
Q: https://blog.bobthedeveloper.io/introduction-to-protocol-oriented-programming-in-swift-b358fe4974f
R: https://www.slideshare.net/yltastep/protocoloriented-programming-in-swift
S: https://medium.com/@agoiabeladeyemi/protocol-in-swift-with-practical-examples-8b955268ce39
T: Hoffman, J. (2019). Swift Protocol-Oriented Programming – Fourth Edition. Pakt Publishing, Birmingham, UK.
U: https://www.tensorflow.org/swift/tutorials/protocol_oriented_generics
V: https://team.goodeggs.com/overriding-swift-protocol-extension-default-implementations-d005a4428bda
W: https://medium.com/@bobgodwinx/swift-associated-type-design-patterns-6c56c5b0a73a
X: http://www.russbishop.net/swift-associated-types
Last Updated: 31/05/2019
Comments