The original article – Protocol Oriented Programming in Swift: Is it better than Object Oriented Programming? – was published on appcoda.com.
Introduction
We’re going to talk in-depth about protocol-oriented programming (POP) using Swift 4 in this article. This post is the second and final article in a two part series. If you haven’t read the first, introductory article, please do so before continuing onwards. Today, we’ll: discuss why Swift is considered a “protocol-oriented” language, compare POP and object-oriented programming (OOP), compare value semantics and reference semantics, consider local reasoning, implement delegation with protocols, use protocols as types, use protocol polymorphism, review my real-world POP Swift code, and finally, discuss why I’ve not bought 100% into POP. Download the source code from the article so you can follow along: There are 2 playgrounds and one project on GitHub, both in Xcode 9 format and written in Swift 4.
A note on WWDC links
I’ve referenced at least three links into Apple Worldwide Developers Conference (WWDC) videos in this two-part series on POP. Clicking on these links takes you directly into a specific section of a video (and oftentimes starts playing that video at that time index), if you’re using Safari. If you’re not using Safari, you’ll have to glean the time index from the link itself, scan through the video, and/or look at the transcript for each video.
Why is Swift “protocol-oriented?”
Remember that in my introductory article on POP that I mentioned Apple’s assertion that “at its heart, Swift is protocol-oriented.” Let’s talk a little bit about comparative languages and then I’ll show you how Swift is protocol-oriented.
It’s good for you to know a bit about other languages as you may find yourself at some point, for example, linking C++ libraries into your iOS apps. A number of my iOS and OS X apps link with C++ libraries as I have Windows versions of those apps. I’ve supported some seriously “platform-independent” apps over the years.
OOP languages have supported interfaces for years, and interfaces are similar to Swift protocols, but not equivalent.
Interfaces in these languages specify what methods and/or properties must be implemented by the class and/or struct that adopts (conforms to) a specific interface. I used “and/or” because, for example, C++ has no concept of an interface; rather one would use an abstract class; and, FYI, a C++ struct
can inherit from a class. C# interfaces allow the specification of properties and methods and a struct
can adopt an interface. Objective-C uses the term “protocol” instead of “interface,” and protocols can require methods and properties, but only a class can adopt a protocol, not a struct
.
These interfaces and the Objective-C protocol don’t implement anything, they just specify requirements, serving as “blueprints” for the adopting classes and/or structs.
Protocols form the foundation of the Swift standard library. As I showed you in my first article on POP, protocols are the key ingredient in the POP recipe (methodology or paradigm).
Swift protocols have something that none of these other languages support: protocol extensions. From Apple:
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. …
By creating an extension on the protocol, all conforming types automatically gain this method implementation without any additional modification. …
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. …
You saw me use protocol extensions in my last article and you will again today. They are the secret sauce that makes Swift POP so powerful.
Protocols were important in iOS even before Swift. Remember my discussion of protocols like UITableViewDataSource
and UITableViewDelegate
that long-time iOS developers have adopted for years? Then also remember your everyday Swift programming language code.
It would be impossible to write Swift code without making use of protocols in the standard library. Take Array
(a struct
inheriting from 10 protocols), Bool
(a struct
inheriting from 7 protocols), Comparable
(a protocol that inherits from another protocol and is the ancestor of many other Swift types), and Equatable
(a protocol from which many Swift protocols and types inherit).
Spend some time reviewing the Swift standard library, following links to all the types, protocols, operators, globals, and functions. Make sure you look for the “Inheritance” sections on most all of the pages, and be sure to click on the “VIEW PROTOCOL HIERARCHY ->” links. You’ll see lots of protocols, links to protocol definitions, and diagrams of protocol inheritance.
Remember one very important point: Most code in the iOS (and OS X) SDKs comes in the form of class hierarchies. I believe many of the core frameworks we use are still written in Objective-C (and some C++ and C), like Foundation
and UIKit
. Take the UIButton
class found in UIKit
. Follow the inheritance tree using the “Inherits From” links found in Apple’s documentation pages from the leaf UIButton
up to the root NSObject
here: UIButton
to UIControl
to UIView
to UIResponder
to NSObject
. Visually, it looks like this:
POP and OOP
The merits of OOP have been long extolled. Instead of regurgitating and defining them, I’ll list them here and provide a link to the detailed explanation I wrote about OOP as materialized in Swift here. (Note that if you’re reading this article and don’t understand OOP, I suggest you get up to speed before even considering POP.)
OOP benefits include reusability, inheritance, maintainability, hiding complexity (encapsulation), abstraction, polymorphism, access control to a class, and access control to a class’s properties and methods. There are probably a few catch-phrases I’ve left out as so much, probably too much, has been said about OOP.
Simply put and without quibbling over inanities, OOP and POP share most of these attributes, with one major exception: Classes can only inherit from one other class while protocols can inherit from multiple protocols. Remember that we’re limiting our OOP and POP discussion to the Swift language (i.e., C++ supports multiple inheritance but Swift doesn’t).
Because of the way OOP inheritance works, it’s probably a best practice to be limited to single inheritance. Multiple inheritance can get very messy very quickly.
On the other hand, protocols can adopt from one to many other protocols.
Why the push towards protocols? When large class hierarchies are built, a lot of properties and functionality can get inherited. Developers tend to add (and keep adding) general features to the top of the hierarchy — mainly to high-level ancestor classes. Mid-level and leaf-level classes tend to be more specific and have functionality added, but probably not added as much as should be. Ancestor classes tend to be buckets for new functionality; oftentimes they become “polluted” or “bloated” with too many, extra, extraneous, and/or unrelated features. Mid-level and leaf-level classes end up inheriting a lot more features than they need.
These concerns about OOP aren’t written in stone. A good developer can avoid many of the OOP pitfalls I just enumerated. It takes time and practice and experience. For example, developers can overcome the functional bloat problem by adding instances of other classes as members to the classes they’re building rather than inheriting from those other classes (i.e., composition over inheritance).
A bonus from Swift POP: protocols can be adopted by value types like struct
and enum
. Of course protocols can be adopted by reference types, i.e., classes. We’ll discuss some advantages to using value types soon.
I do have some concerns about multiple protocol conformance. Are we trading one type of complexity and difficulty in understanding our code for another, i.e., trading the “vertical” complexity of OOP’s inheritance for the “horizontal” complexity of POP inheritance?
Compare the class inheritance hierarchy for UIButton
to the protocol inheritance bloat of the Swift Array
:
Local reasoning can’t apply to either of these complex entities. There are just to many pieces and relationships.
Array
image source: https://swiftdoc.org/v3.1/type/Array/hierarchy/
Value semantics versus reference semantics
As I mentioned in the last article, Apple is evangelizing for the related concepts of POP and value semantics. (They’re pushing one more ideal related to POP, as we’ll see below.) I showed you code last time, and will again today, that should make the meaning of “reference semantics” and “value semantics” obvious. Look at the code for my ObjectThatFlies
protocol in last week’s post and today at my List
, FIFO
, LIFO
, and related protocols.
That Apple engineer, Alex, says that we should use “value types and protocols to make your app better.” According to a section of code documentation entitled “Understanding Value Semantics” accompanying an Apple sample playground:
Sequences and collections in the standard library use value semantics, which makes it easy to reason about your code. Every variable has an independent value, and references to that value aren’t shared. For example, when you pass an array to a function, that function can’t accidentally modify the caller’s copy of the array.
Of course this applies to all types that use value semantics. I urge you to download and walk through this whole playground.
I’m not dropping classes, which exhibit reference semantics. I can’t. I have too much of my own class-based code. I help my customers with their millions of lines of class-based code. I do agree that value semantics are generally safer than reference semantics. As I develop new code, or refactor existing code, I’ll consider making the leap on a case-by-case basis.
Reference semantics can lead to “unintended sharing” of data by class instances (references). Some call this “unintended mutation.” There are techniques we can use to minimize the side effects of reference semantics, but I will grant that I’m looking more and more to value semantics.
Value semantics allow us to avoid unintended changes to variables, which is a really good thing. We’re avoiding side effects due to unintended mutation because “Every variable has an independent value, and references to that value aren’t shared.”
Since the Swift struct
is a value type and can adopt protocols, and Apple’s pushing POP over OOP, there you have the reasoning behind the company’s evangelization for “protocol(s) and value oriented programming.”
Local reasoning
Let’s talk about a laudable goal, something Apple calls “local reasoning.” The topic was introduced by an Apple engineer named Alex at WWDC 2016 – Session 419, “Protocol and Value Oriented Programming in UIKit Apps.” Make this a third concept that Apple is promoting in conjunction with its push for POP. I showed you code last time, and will again today, whose meaning should be obvious. Look at the code for my ObjectThatFlies
protocol and my Line
class in last week’s post and today at my List
, FIFO
, LIFO
, and related protocols.
I thought this was old news. Years ago, professors, coworkers, mentors, fellow developers, etc., had been talking about techniques like: never writing a function that doesn’t fit on one screen without scrolling (i.e., no longer than one printed page, and preferably shorter); breaking large functions into smaller ones; breaking large code files into smaller ones; using meaningful variable names; taking time for design before banging on a keyboard; making judicious and consistent use of spacing and indentation; the grouping of related properties and behaviors into classes and/or structs; and, grouping related classes and/or structs into organizational units like frameworks or libraries. But it was brought it up while Apple was explaining POP.
Alex told us:
Local reasoning means that when you look at the code, right in front of you, you don’t have to think about how the rest of your code interacts with that one function. You may have had this feeling before and that’s just a name for that feeling. For example, maybe when you just joined a new team and you have tons of code to look at but very little context, can you understand what’s going on in that single function? And so the ability to do that is really important because it makes it easier to maintain, easier to write, and easier to test, and easier to write the code the first time.
Ah, er, ah… Has that ever happened to you? I’m being facetious, because I have read some really well-written code developed by other people. I’ve written some very readable code. To be honest, after 30 years in the industry, most of the existing code I’ve had to support and/or enhance has not given me that warm and fuzzy feeling that Alex is describing. Oftentimes, I’ve become quite frustrated because I’ll look at a piece of code and have no idea what’s going on.
The Swift language source code is open/public. Please just give a quick glance at the following function, and please don’t spend three hours trying to understand it.
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 |
public mutating func next() -> Any? { if index + 1 > count { index = 0 // ensure NO ivars of self are actually captured let enumeratedObject = enumerable var localState = state var localObjects = objects (count, useObjectsBuffer) = withUnsafeMutablePointer(to: &localObjects) { let buffer = AutoreleasingUnsafeMutablePointer<AnyObject?>($0) return withUnsafeMutablePointer(to: &localState) { (statePtr: UnsafeMutablePointer<NSFastEnumerationState>) -> (Int, Bool) in let result = enumeratedObject.countByEnumerating(with: statePtr, objects: buffer, count: 16) if statePtr.pointee.itemsPtr == buffer { // Most cocoa classes will emit their own inner pointer buffers instead of traversing this path. Notable exceptions include NSDictionary and NSSet return (result, true) } else { // this is the common case for things like NSArray return (result, false) } } } state = localState // restore the state value objects = localObjects // copy the object pointers back to the self storage if count == 0 { return nil } } defer { index += 1 } if !useObjectsBuffer { return state.itemsPtr![index] } else { switch index { case 0: return objects.0!.takeUnretainedValue() case 1: return objects.1!.takeUnretainedValue() case 2: return objects.2!.takeUnretainedValue() case 3: return objects.3!.takeUnretainedValue() case 4: return objects.4!.takeUnretainedValue() case 5: return objects.5!.takeUnretainedValue() case 6: return objects.6!.takeUnretainedValue() case 7: return objects.7!.takeUnretainedValue() case 8: return objects.8!.takeUnretainedValue() case 9: return objects.9!.takeUnretainedValue() case 10: return objects.10!.takeUnretainedValue() case 11: return objects.11!.takeUnretainedValue() case 12: return objects.12!.takeUnretainedValue() case 13: return objects.13!.takeUnretainedValue() case 14: return objects.14!.takeUnretainedValue() case 15: return objects.15!.takeUnretainedValue() default: fatalError("Access beyond storage buffer") } } } |
Can you honestly say you understood this code after taking a glance at it? I didn’t. I had to spend time reading through it several times and looking up things like function definitions. In my experience, code like this is ubiquitous and often just darn unavoidable.
Now let’s consider understanding a Swift type (granted, it’s not a function). Look at Swift’s Array
definition. My goodness. It inherits from ten protocols, BidirectionalCollection
, Collection
, CustomDebugStringConvertible
, CustomReflectable
, CustomStringConvertible
, ExpressibleByArrayLiteral
, MutableCollection
, RandomAccessCollection
, RangeReplaceableCollection
, and Sequence
. Click the “VIEW PROTOCOL HIERARCHY ->” link button and — holy cow, Batman! — look at that spaghetti.
The bottom line is that it’s a lot easier to achieve local reasoning if you’re able to start a new project and have your entire team buy in voluntarily to a consistent set of guidelines for best practices during software development. Refactoring, if done to small amounts of code at a time, presents another opportunity for achieving local reasoning. For me, like most everything else, refactoring is to be done judiciously and carefully, with Goldilocks motivation: not too hot and not too cold.
Keep in mind that’s you’re almost always going to be faced with some very complex business logic that, when translated into code, is going to require that a new team member receive some form of domain knowledge training and/or indoctrination before she/he can read your code fluently. She/he is most likely going to have to look up the definitions of some functions, classes, structs, enums, variables, etc.
Delegation and protocols
Protocols play and integral part in the delegation design pattern, ubiquitous throughout iOS. We don’t need to rehash it here. You can read my post on the subject here.
Protocols as types and protocol polymorphism
I’m not going to spend a whole lot of time on these topics. I’ve told you a whole lot about protocols and showed you a lot of code. As an assignment, I want you to research how important it is, and how much flexibility it affords us, to be able to use Swift protocols as types (as in delegation), and because they exhibit polymorphism (i.e., if you had a factory model with many structs all conforming to protocols in the same family of protocols).
Protocols as types
Remember that in my article on delegates, I defined this property:
1 |
var delegate: LogoDownloaderDelegate? |
where LogoDownloaderDelegate
is a protocol. I then called one of the protocol’s methods.
Protocol polymorphism
Just as in OOP, we can interact with multiple types conforming to the same family of protocols through a type conforming to the families’ parent protocol. This is best demonstrated through example 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
protocol Top { var protocolName: String { get } } protocol Middle: Top { } protocol Bottom: Middle { } struct TopStruct : Top { var protocolName: String = "TopStruct" } struct MiddleStruct : Middle { var protocolName: String = "MiddleStruct" } struct BottomStruct : Bottom { var protocolName: String = "BottomStruct" } let top = TopStruct() let middle = MiddleStruct() let bottom = BottomStruct() var topStruct: Top topStruct = bottom print("\(topStruct)\n") // prints "BottomStruct(protocolName: "BottomStruct")" topStruct = middle print("\(topStruct)\n") // prints "MiddleStruct(protocolName: "MiddleStruct")" topStruct = top print("\(topStruct)\n") // prints "TopStruct(protocolName: "TopStruct")" let protocolStructs:[Top] = [top,middle,bottom] for protocolStruct in protocolStructs { print("\(protocolStruct)\n") } // prints to console: // // TopStruct(protocolName: "TopStruct") // // MiddleStruct(protocolName: "MiddleStruct") // // BottomStruct(protocolName: "BottomStruct") |
Real-world, UIKit
applications of protocols
Let’s get down to brass tacks and write some Swift 4 code that’s being used in production iOS apps — my own apps. This code should get you started down the path of thinking in terms of building and/or extending code using protocols. What do we call that? “Protocol-oriented programming” or POP as I’ve been harping on continuously throughout two articles.
I’ve chosen to show you how to extend or enhance — whichever term you prefer — UIKit
classes because 1) you’re probably so used to dealing with them and 2) it’s a bit more tricky to extend iOS SDK classes like UIView
than it is if you were starting from your own custom classes. And… we already covered extending custom classes previously.
I wrote all this UIView
enhancement code in an Xcode 9 project based on the Single View App
template.
Note I’m enhancing UIView
with default protocol extensions
— and the key to doing this safely is with what Apple calls “conditional conformance” (see also here). Since I’m only interested in extending the UIView
class, let’s make the compiler enforce my interest and make it a requirement.
I often use the UIView
class as a container to organize other UI elements in my app screens. Sometimes I use those container views ornamentally (visibly) to improve to look, feel, and layout of my UI.
Here’s an ani-GIF showing the result of applying three protocols I’ll create below to customize the appearance of the UIView
class:
Notice that I’m following the “local reasoning” principle here. Each one of my protocol-based solutions can fit on one screen/page without scrolling. I hope you can read each as they don’t contain much code, but are powerful nonetheless.
Adding a default border to UIView
Suppose I’d like a bunch of instances of UIView
to all have the same border — like in an app with a color theme. An example of this class is shown as the green colored top view in the image I just presented above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
protocol SimpleViewWithBorder {} // SAFETY: Constrain "addBorder" only to UIViews. extension SimpleViewWithBorder where Self : UIView { func addBorder() -> Void { layer.borderColor = UIColor.green.cgColor layer.borderWidth = 10.0 } } class SimpleUIViewWithBorder : UIView, SimpleViewWithBorder { } |
To create, configure, and display an instance of SimpleUIViewWithBorder
, I put the following code in an IBAction
in my ViewController
subclass:
1 2 3 4 5 6 7 8 |
... @IBAction func addViewButtonTapped(_ sender: Any) { let customFrame0 = CGRect(x: 110, y: 100, width: 100, height: 100) let customView0 = SimpleUIViewWithBorder(frame: customFrame0) customView0.addBorder() self.view.addSubview(customView0) ... |
I didn’t have to create a special initializer for this subclass of UIView
.
Adding a default background color to UIView
Say I want several instances of UIView
to all have the same background color. An example of this class is shown as the blue colored middle view in the image I just presented above. Notice I’m moving one step closer to a configurable UIView
. Do you see where?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
protocol ViewWithBackground { var customBackgroundColor: UIColor { get } } extension ViewWithBackground where Self : UIView { func addBackgroundColor() -> Void { backgroundColor = customBackgroundColor } } class UIViewWithBackground : UIView, ViewWithBackground { let customBackgroundColor: UIColor = .blue } |
To create, configure, and display an instance of UIViewWithBackground
, I put the following code in an IBAction
in my ViewController
subclass:
1 2 3 4 5 6 |
... let customFrame1 = CGRect(x: 110, y: 210, width: 100, height: 100) let customView1 = UIViewWithBackground(frame: customFrame1) customView1.addBackgroundColor() self.view.addSubview(customView1) ... |
I didn’t have to create a special initializer for this subclass of UIView
.
Adding a configurable border color to UIView
Now I want to be able to configure the border, both its color and thickness, for multiple instances of UIView
. With the following implementation I can create as many groups of views, with differing border colors and thicknesses, as I want. An example of this class is shown as the red colored bottom view in the image I just presented above. There’s a cost to adding configurable properties to my protocol. I need to be able to initialize those properties, so I’ve added an init
to my protocol. That means I’ve got to be able to call the UIView
initializer, too. You’ll see when you read the 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 |
protocol ViewWithBorder { var borderColor: UIColor { get } var borderThickness: CGFloat { get } init(borderColor: UIColor, borderThickness: CGFloat, frame: CGRect) } extension ViewWithBorder where Self : UIView { func addBorder() -> Void { layer.borderColor = borderColor.cgColor layer.borderWidth = borderThickness } } class UIViewWithBorder : UIView, ViewWithBorder { let borderColor: UIColor let borderThickness: CGFloat // This initializer is required by UIView. required init(borderColor: UIColor, borderThickness: CGFloat, frame: CGRect) { self.borderColor = borderColor self.borderThickness = borderThickness super.init(frame: frame) } // This initializer is required by UIView. required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } |
To create, configure, and display an instance of UIViewWithBorder
, I put the following code in an IBAction
in my ViewController
subclass:
1 2 3 4 5 6 |
... let customFrame2 = CGRect(x: 110, y: 320, width: 100, height: 100) let customView2 = UIViewWithBorder(borderColor: .red, borderThickness: 10.0, frame: customFrame2) customView2.addBorder() self.view.addSubview(customView2) ... |
What I didn’t want to do
I didn’t create a generic piece of code like this:
1 2 3 4 5 6 7 8 9 10 11 |
extension UIView { func addBorder() { // put code here } func addBackgroundColor() { // put code here } } |
This might be a valid in some situations, but I feel that it’s painting with with a very broad stoke. I would have lost a lot of granular control. There’s also a tendency for this type of construct to become a dumping ground for every enhancement related to UIView
, in other words, a big bloated piece of code. The bigger it gets, the less readable and supportable it becomes.
I used classes — reference types — to subclass UIView
in all the above UIKit
-based helper protocols. Subclassing gave me direct access to everything in the parent UIView
class, making my code clear, short, simple, and readable. If I would’ve used struct
, my code would’ve been much more verbose. I leave it to you as an exercise to determine why.
What I can do
Keep in mind that all these default protocol extensions
can be overridden in the extended class. This is best explained with an example and picture:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
protocol SimpleViewWithBorder {} extension SimpleViewWithBorder where Self : UIView { func addBorder() -> Void { layer.borderColor = UIColor.green.cgColor layer.borderWidth = 10.0 } } class SimpleUIViewWithBorder : UIView, SimpleViewWithBorder { // OVERRIDE OF DEFAULT EXTENSION func addBorder() -> Void { layer.borderColor = UIColor.darkGray.cgColor layer.borderWidth = 20.0 } } |
Notice my comment in the SimpleUIViewWithBorder
immediately above in the class definition. Look at the top view in the ani-GIF shown here:
Real-world, generic data structures based on protocols
I’m very proud of the minimal amount of POP code I had to write to create fully-functional and generic stack and queue data structures that I’m now using in my own apps. For background on using Swift generics, please read my article here.
Notice that I used protocol inheritance to help me build the more specialized FIFO
and LIFO
protocols from the more abstract List
protocol. I then took advantage of protocol extensions to materialize the Queue
and Stack
value types. You can see instances of these struct
types at work in the Xcode 9 playground shown below.
I wanted to show you how Apple advises that customization of protocols should be achieved mainly through adoption of other protocols, so I created the ListSubscript
, ListPrintForwards
, ListPrintBackwards
, and ListCount
protocols. They are currently simplistic, but would show promise in an actual app.
This multiple adoption of other protocols is meant to allow developers to add features to a codebase without “polluting” or “bloating” it with too many, extra, extraneous, and/or unrelated features. Each of these helper protocols is standalone. If added as classes above leaf level in an inheritance hierarchy, these features would automatically be inherited by at least some other classes in the family tree depending on their positions in the tree.
I’ve told you enough about POP for you to read and understand this code. I’ll grant you one hint about how I made my data structure types generic: the definition of “associated types:”
When defining a protocol, it’s sometimes useful to declare one or more associated types as part of the protocol’s definition. An associated type gives a placeholder name to a type that is used as part of the protocol. The actual type to use for that associated type isn’t specified until the protocol is adopted. Associated types are specified with the associatedtype
keyword.
Here’s the 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 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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
protocol ListSubscript { associatedtype AnyType var elements : [AnyType] { get } } extension ListSubscript { subscript(i: Int) -> Any { return elements[i] } } protocol ListPrintForwards { associatedtype AnyType var elements : [AnyType] { get } } extension ListPrintForwards { func showList() { if elements.count > 0 { var line = "" var index = 1 for element in elements { line += "\(element) " index += 1 } print("\(line)\n") } else { print("EMPTY\n") } } } protocol ListPrintBackwards { associatedtype AnyType var elements : [AnyType] { get } } extension ListPrintBackwards { func showList() { if elements.count > 0 { var line = "" var index = 1 for element in elements.reversed() { line += "\(element) " index += 1 } print("\(line)\n") } else { print("EMPTY\n") } } } protocol ListCount { associatedtype AnyType var elements : [AnyType] { get } } extension ListCount { func count() -> Int { return elements.count } } protocol List { associatedtype AnyType var elements : [AnyType] { get set } mutating func remove() -> AnyType mutating func add(_ element: AnyType) } extension List { mutating func add(_ element: AnyType) { elements.append(element) } } protocol FIFO : List, ListCount, ListPrintForwards, ListSubscript { } extension FIFO { mutating func remove() -> AnyType { if elements.count > 0 { return elements.removeFirst() } else { return "******EMPTY******" as! AnyType } } } struct Queue<AnyType>: FIFO { var elements: [AnyType] = [] } var queue = Queue<Any>() queue.add("Bob") queue.showList() queue.add(1) queue.showList() queue.add(3.0) _ = queue[0] // subscript gives us "Bob" _ = queue.count() // 3 queue.showList() queue.remove() queue.showList() queue.remove() queue.showList() queue.remove() queue.showList() _ = queue.count() // 0 (zero) protocol LIFO : List, ListCount, ListPrintBackwards, ListSubscript { } extension LIFO { mutating func remove() -> AnyType { if elements.count > 0 { return elements.removeLast() } else { return "******EMPTY******" as! AnyType } } } struct Stack<AnyType>: LIFO { var elements: [AnyType] = [] } var stack = Stack<Any>() stack.add("Bob") stack.showList() stack.add(1) stack.showList() stack.add(3.0) _ = stack[0] // subscript gives us 3 _ = stack.count() // 3 stack.showList() stack.remove() stack.showList() stack.remove() stack.showList() stack.remove() stack.showList() _ = stack.count() // 0 (zero) |
Console output from previous code snippet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Bob Bob 1 Bob 1 3.0 1 3.0 3.0 EMPTY Bob 1 Bob 3.0 1 Bob 1 Bob Bob EMPTY |
I haven’t bought 100% into POP
In one of the WWDC videos on POP, an engineer/presenter says “we have a saying in Swift. Don’t start with a class. Start with a protocol.” Yeah, well, maybe. This guy gets into some long-winded discussion about getting a binary search to work using protocols. I somehow doubt this is many of my readers’ most pressing concern right now. Are you losing sleep over it?
This smells like a contrived problem cooked up to be supposedly looking for a POP solution. Maybe it is valid; maybe the solution has merit; I don’t know. My time is precious and I don’t have a lot to spare for ivory tower theorizing. If looking at some code and understanding it takes more than 5 minutes, then I know it doesn’t fall under the category of Apple’s “local reasoning” principle.
Its always a good idea to keep an open mind to new methodologies when you’re a software developer like me, and managing complexity is a mainstay of your livelihood. I am by no means opposed to making money, but it’s good to put things in perspective. Always remember that Apple is a business, a big business, on a mission to make lots of money, with last Friday’s total market cap value at closing of about $837 BILLION, with several hundred billion in cash and cash equivalents. It’s in their interest to get everyone roped into Swift, and one way companies try to rope people into their ecosystems is to offer products and services that no one else supposedly offers. Yeah, yeah, yeah, Swift is open source, but Apple makes big money from the App Store and apps are what makes all of Apple’s devices useful, and many, many app developers are moving to Swift.
I don’t see any justification for becoming a POP-only programmer. I see some problems with POP just as I do with many other technologies, even OOP. We’re modeling reality. At best we’re approximating reality. There are no perfect solutions. Throw POP into your developer’s toolbox along with the rest of the few good ideas people have come up with over the years.
Conclusion
Now that I’ve got some of that 30-years-of-software-experience cynism off my chest, I can speak more calmly. You should look into protocols and POP. Above all, design and write your own POP code.
I’ve already spent quite a bit of time experimenting with POP and am already using the protocols I presented in this article, like SimpleViewWithBorder
, ViewWithBackground
, ViewWithBorder
, List
, FIFO
, and LIFO
, in my own production apps. POP is powerful.
As I mentioned in the introductory post to this article, learning and adopting a new technology like POP is not an all or nothing proposition. POP and OOP can not only co-exist, they can compliment each other.
EXERCISE: Take the OOP class hierarchy I created — IBMessageBoxDismiss
, IBMessageBoxDismissAndOK
, and IBMessageBoxDismissAndOKWithTextField
, and make it POP-based by by using structs
and protocols.
So get out there, experiment, practice, study… Above all, make the best of life and work and enjoy yourselves!
Hello, this is really a nice post. However, I got a little confused in “Look at Swift’s Array definition. My goodness. It inherits from eleven protocols, …”. Actually there is ten protocols that Array inherits from. I doubt if this is a minor typo or something else? Hope to receive your kind response, thanks!
You’re right.
Array
inherits from ten protocols. It was a typo. Guess I can’t count. 🙂