NOTE: Learn all about protocol-oriented programming in Swift here, here, and here.
[Download two Xcode 9 playgrounds with full Swift 4 source from GitHub.]
We’re going to talk about “protocols” in the Swift 4 language today. I’ll explain them conceptually, and then we’ll start coding protocols with a simple example. We’ll then create our own versions of the Apple built-in Equatable
and Comparable
protocols, and apply them to two real-world classes, one for tracking financial securities and one for representing geometric lines/vectors. Finally, we’ll test our geometric “Line” class in a type of Swift playground that supports rendering user interface components (like UIView
) live in the simulator. But first, please ponder the layman’s definition of the word “protocol” before moving on:
… The official procedure or system of rules governing affairs of state or diplomatic occasions. …
The accepted or established code of procedure or behaviour in any group, organization, or situation. …
A procedure for carrying out a scientific experiment…
Swift Protocols
Apple’s “The Swift Programming Language (Swift 4.0.3)” documentation states:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol. …
For me, protocols are one of the most important tools we have to bring some order to the inherent chaos that is software. Protocols give us the ability to require that some — even many — of our classes contain/perform certain minimum or required properties and/or functionality, especially if assigned to base classes in class hierarchies.
Adopting a protocol
Consider the layman’s definition (above) and now the computer science definition of “protocol” together. Then remember my discussion of Swift generics where I made my “Person” class conform to (adopt) the Equatable
protocol. You can read that article, but I’ll remind you what I did:
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 |
class Person : Equatable { var name:String var weight:Int var sex:String init(weight:Int, name:String, sex:String) { self.name = name self.weight = weight self.sex = sex } static func == (lhs: Person, rhs: Person) -> Bool { if lhs.weight == rhs.weight && lhs.name == rhs.name && lhs.sex == rhs.sex { return true } else { return false } } } |
Apple states that, “Custom types state that they adopt a particular protocol by placing the protocol’s name after the type’s name, separated by a colon, as part of their definition.” That’s just what I did:
1 |
class Person : Equatable |
You can conceptualize a protocol as a contract or promise that you can apply to a class, structure, or enumeration. I have entered my “Person” class into a contract with the Equatable
protocol, and the “Person” class promises to fulfill the contract by implementing the methods or member variables that Equatable
requires be materialized or fulfilled, i.e., implemented.
The Equatable
protocol doesn’t implement anything. It only specifies what methods and/or member variables must be implemented by the class, structure, or enumeration that adopts (conforms to) Equatable
. (Some protocols implement functionality through “extensions,” but that is beyond the scope of this discussion.)
Defining a protocol
Protocols are best understood from example code. I’m going to implement my own version of Equatable
to show you how protocols work:
1 2 3 4 5 6 |
protocol IsEqual { static func == (lhs: Self, rhs: Self) -> Bool static func != (lhs: Self, rhs: Self) -> Bool } |
Remember that my “IsEqual” protocol doesn’t implement the ==
and !=
operators. “IsEqual” requires that adopters of the protocol implement their own ==
and !=
operators.
To show you how widely applicable is a protocol like my “IsEqual” (or Equatable
), let’s get creative and build a class that will model a position in a financial stock. Each instance of my “StockAtClosing” class represents the value of the number of shares of a particular security on a particular day after the stock market’s closing bell.
Your assignment
I’m not going to explain the domain knowledge particulars of the “StockAtClosing” class. I want you to take the time to understand the purpose of the class, i.e., look up definitions for the member variables “symbol,” “shares,” “price,” and “value.” Find out things like why I marked the timestamp for the stock just when the market closes. Hint, hint 😉 … Look at a daily history of Apple’s stock price.
I am going to discuss how “StockAtClosing” adopts the “IsEqual” protocol.
Custom class adopting a custom protocol
If not familiar with the stock market, have you done your research on financial securities and exchanges? You should…
Before looking at my finished “StockAtClosing” class, what if I had adopted the “IsEqual” protocol without implementing the required ==
and !=
operators? I would’ve received Swift compile-time errors — and informative ones:
1 2 3 4 5 6 7 8 9 |
error: Protocols.playground:38:7: error: type 'StockAtClosing' does not conform to protocol 'IsEqual' class StockAtClosing : IsEqual ^ ... Protocols.playground:21:17: note: protocol requires function '==' with type '(StockAtClosing, StockAtClosing) -> Bool'; do you want to add a stub? static func == (lhs: Self, rhs: Self) -> Bool ^ |
Here’s my “StockAtClosing” class and some test output:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
class StockAtClosing : IsEqual { var symbol:String var shares:Int var price:Float var date:Date var value:Float { get { return Float(shares) * price } } init() { symbol = "XXXX" shares = 0 price = 0.00 date = Date() date = setClosingDateTime() } init(symbol:String, shares:Int, price:Float) { self.symbol = symbol self.shares = shares self.price = price self.date = Date() self.date = setClosingDateTime() } static func == (lhs:StockAtClosing, rhs:StockAtClosing) -> Bool { if ( lhs.symbol == rhs.symbol && lhs.shares == rhs.shares && lhs.price == rhs.price && lhs.date == rhs.date) { return true } else { return false } } static func != (lhs:StockAtClosing, rhs:StockAtClosing) -> Bool { if ( lhs.symbol != rhs.symbol || lhs.shares != rhs.shares || lhs.price != rhs.price || lhs.date != rhs.date) { return true } else { return false } } func setClosingDateTime() -> Date { let gregorian = Calendar(identifier: .gregorian) let now = Date() var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now) // Change the time to 4:00:00 PM, stock market close. components.hour = 4 components.minute = 00 components.second = 0 return gregorian.date(from: components)! } } // end class StockAtClosing : IsEqual let apple = StockAtClosing(symbol: "AAPL", shares: 99, price: 178.46) let apple1 = StockAtClosing(symbol: "AAPL", shares: 100, price: 178.46) let ibm = StockAtClosing(symbol: "IBM", shares: 50, price: 135.20) let ibm1 = StockAtClosing(symbol: "IBM", shares: 50, price: 135.20) apple == apple1 // returns false apple != apple1 // returns true ibm == ibm1 // returns true |
Adopting multiple protocols
When I first started this article, I got greedy and had thought about manually creating a protocol that mirrored both Apple’s Equatable
and Comparable
protocols:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
protocol IsEqualAndComparable { static func == (lhs: Self, rhs: Self) -> Bool static func != (lhs: Self, rhs: Self) -> Bool static func > (lhs: Self, rhs: Self) -> Bool static func < (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool } |
I realized that I should divide and conquer and make my code as flexible as possible. Why not? Apple states that multiple protocols can be adopted by a class, structure, or enumeration, as we’ll see below. Here are the two protocols I came up with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
protocol IsEqual { static func == (lhs: Self, rhs: Self) -> Bool static func != (lhs: Self, rhs: Self) -> Bool } protocol Comparable { static func > (lhs: Self, rhs: Self) -> Bool static func < (lhs: Self, rhs: Self) -> Bool static func >= (lhs: Self, rhs: Self) -> Bool static func <= (lhs: Self, rhs: Self) -> Bool } |
Remember your algorithms
An important skill you need to hone is the development of algorithms, and translating them into code. I guarantee that one day somebody is going to give you a verbal description of some complex process and ask you to encode it. There’s often a huge gap between a human language description of some procedure and realizing that procedure in software. I was reminded of that when I got the idea to apply my “IsEqual” and “Comparable” protocols to a class representing a line (vector). I remembered that calculating the length of a line is based on the Pythagorean Theorem (see here and here), and line length was key to comparing lines with the ==
, !=
, <
, >
, <=
, and >=
operators. My "Line" class would come in handy in, for example, a drawing app or game, where the user taps on two locations on screen to create a line between two points.
Custom class adopting multiple protocols
Here's my "Line" class, which adopts two protocols, "IsEqual" and "Comparable:"
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
class Line : IsEqual, Comparable { var beginPoint:CGPoint var endPoint:CGPoint init() { beginPoint = CGPoint(x: 0, y: 0); endPoint = CGPoint(x: 0, y: 0); } init(beginPoint:CGPoint, endPoint:CGPoint) { self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y ) self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y ) } // The line length formula is based on the Pythagorean theorem. func length () -> CGFloat { let length = sqrt( pow(endPoint.x - beginPoint.x, 2) + pow(endPoint.y - beginPoint.y, 2) ) return length } static func == (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() == rightHandSideLine.length()) } static func != (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() != rightHandSideLine.length()) } static func > (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() > rightHandSideLine.length()) } static func < (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() < rightHandSideLine.length()) } static func >= (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() >= rightHandSideLine.length()) } static func <= (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() <= rightHandSideLine.length()) } } // end class Line : IsEqual, Comparable |
Verifying your algorithms
I used a spreadsheet, Apple Numbers, and prepared a visual representation of two vectors to do some basic testing of my "Line" class's length()
method:
Here's my test code for the points shown in the chart above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
let x1 = CGPoint(x: 0, y: 0) let y1 = CGPoint(x: 2, y: 2) let line1 = Line(beginPoint: x1, endPoint: y1) line1.length() // returns 2.82842712474619 let x2 = CGPoint(x: 3, y: 2) let y2 = CGPoint(x: 5, y: 4) let line2 = Line(beginPoint: x2, endPoint: y2) line2.length() // returns 2.82842712474619 line1 == line2 // returns true line1 != line2 // returns false line1 > line2 // returns false line1 <= line2 // returns true |
Testing/prototyping UI with "Single View" Xcode playground template
Did you know that you can prototype and test your user interface (UI) using an Xcode 9 "Single View" playground template? It's pretty awesome -- a great time saver and tool for rapid prototyping. In order to more robustly test my "Line" class, I created just such a playground. Assignment: I want you to try this out yourself before I explain it. I will show you my playground's code, simulator output, and my Swift test statements.
Here's my playground code:
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 40 41 |
import UIKit import PlaygroundSupport class LineDrawingView: UIView { override func draw(_ rect: CGRect) { let currGraphicsContext = UIGraphicsGetCurrentContext() currGraphicsContext?.setLineWidth(2.0) currGraphicsContext?.setStrokeColor(UIColor.blue.cgColor) currGraphicsContext?.move(to: CGPoint(x: 40, y: 400)) currGraphicsContext?.addLine(to: CGPoint(x: 320, y: 40)) currGraphicsContext?.strokePath() currGraphicsContext?.setLineWidth(4.0) currGraphicsContext?.setStrokeColor(UIColor.red.cgColor) currGraphicsContext?.move(to: CGPoint(x: 40, y: 400)) currGraphicsContext?.addLine(to: CGPoint(x: 320, y: 60)) currGraphicsContext?.strokePath() currGraphicsContext?.setLineWidth(6.0) currGraphicsContext?.setStrokeColor(UIColor.green.cgColor) currGraphicsContext?.move(to: CGPoint(x: 40, y: 400)) currGraphicsContext?.addLine(to: CGPoint(x: 250, y: 80)) currGraphicsContext?.strokePath() } } class MyViewController : UIViewController { override func loadView() { let view = LineDrawingView() view.backgroundColor = .white self.view = view } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController() |
Here's the visual output of my view from the playground simulator:
Here's Swift code for testing my "Line" class instances matching the vectors I drew in the playground:
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 |
let xxBlue = CGPoint(x: 40, y: 400) let yyBlue = CGPoint(x: 320, y: 40) let lineBlue = Line(beginPoint: xxBlue, endPoint: yyBlue) let xxRed = CGPoint(x: 40, y: 400) let yyRed = CGPoint(x: 320, y: 60) let lineRed = Line(beginPoint: xxRed, endPoint: yyRed) lineRed.length() // returns 440.454310910905 lineBlue != lineRed // returns true lineBlue > lineRed // returns true lineBlue >= lineRed // returns true let xxGreen = CGPoint(x: 40, y: 400) let yyGreen = CGPoint(x: 250, y: 80) let lineGreen = Line(beginPoint: xxGreen, endPoint: yyGreen) lineGreen.length() // returns 382.753184180093 lineGreen < lineBlue // returns true lineGreen <= lineRed // returns true lineGreen > lineBlue // returns false lineGreen >= lineBlue // returns false lineGreen == lineGreen // returns true |
Wrapping up -- and why I write
I hope you all learned something about protocols -- and about all the other skills a good developer should always be striving to improve. I'd like to share my motivation for publishing this blog.
My goal is to help you become well-rounded software developers who engage in critical thinking, understanding why you want to use certain methodologies and tools. Becoming a good developer, computer scientist, software engineer -- whatever you want to call yourself -- means learning, practicing, and experiencing so much that you evolve into a mensch (yes, that term applies to females as well as males). You need to realize that being a developer means you become much more than a technician who's memorized say, the Swift language, and only knows how to build an application using say, Xcode. What if your employer throws you into Android/Eclipse or C# .NET/Visual Studio development? You need to profoundly and instinctively understand algorithms, troubleshooting, design patterns, testing regimens, frameworks, architectures, compilation, linking... I hope you look up the definitions for these terms -- and hope you see where I'm going with this.
Enjoy!