InfoQ HomepageNewsProtocol-Oriented Programming in Swift
Protocol-Oriented Programming in Swift
This item injapanese
Jun 18, 20155min read
Write for InfoQ
Feed your curiosity.Help 550k+ globalsenior developers
each month stay ahead.Get in touch
At WWDC 2015,Dave Abrahams, of C++/Boost fame and now lead of the Swift Standard Library group at Apple, introduced Swift as aProtocol-oriented language, and showed how protocols can be used to improve your code.
Protocol-oriented programming is an OOP paradigm that prefers the use of protocols (interfaces according to Swift terminology) and structs over classes.
Are classes awesome?
Classes, as they are known in OOP, are used to provide:
- encapsulation
- access control
- abstraction
- namespace
- expressivity
- extensibility.
Actually, Abrahams says, those are all attributes of types, and classes are just one way of implementing a type. Yet, they exact a heavy toll on programmers in that they may cause:
- Implicit sharing, such that if two objects refer a third object, then both can modify it without the other knowing about it. This leads to worarounds such as duplicating the referred object to avoid sharing, which in turn leads to inefficiencies; alternatively, sharing may require using locks to avoid race conditions and this can cause more inefficiency and even lead to deadlocks. What this entails is more complexity, which means more bugs.
- Inheritance issues: in many OOP language, there can be one just superclass, and it has to be chosen at the very start. Changing it later can be extremely hard. A superclass, furthermore, forces any stored property on derived classes and this can make it complex to handle initialization and not to break any invariants that the superclass require. Finally, there are usually limitations to what can be overridden, and how, or when it should not be, and those constraints are usually left to the docs.
- Lost type relationship, which ensues from the conflation of interface and implementation. This usually manifests itself through some base class’ methods where no implementation is possible and thus the necessity to downcast to the concrete derived class in that method’s implementation. This last point is illustrated in the following code snippet:
class Ordered { func precedes(other: Ordered) -> Bool { fatalError("implement me!") }}class Label : Ordered { var text: String = "" ... }class Number : Ordered { var value: Double = 0 override func precedes(other: Ordered) -> Bool { return value < (other as! Number).value }}According to Abrahams, protocol-oriented programming is a better abstraction mechanism in that it allows:
- value types (besides classes)
- static type releationships (besides dynamic dispatch)
- retroactive modeling
- no forcing of data on models
- no initialization burden
- clarity as to what shall be implemented.
Protocol-Oriented Programming
The first step for a new abstraction in Swift should always be a protocol, Abrahams says. He goes on then to rewrite theOrdered class example using the protocols and structs approach in order to show how much cleaner the ensuing implementation is:
protocol Ordered { func precedes(other: Self) -> Bool}struct Number : Ordered { var value: Double = 0 func precedes(other: Number) -> Bool { return self.value < other.value }}In the snippet above, the use ofSelf in theprecedes protocol requirement is what makes it possible that theprecedes method implementation in theNumber class correctly gets the proper parameter and no casting is necessary.
TheSelf requirement has an important implication when it comes to using a protocol that includes it. In fact, if we define abinarySearch method that takes an array ofOrdered instances, we could write the following code:
class Ordered { ... }func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { var lo = 0 var hi = sortedKeys.count while hi > lo { let mid = lo + (hi - lo) / 2 if sortedKeys[mid].precedes(k) { lo = mid + 1 } else { hi = mid } } return lo}On the other hand, if we use a protocol including theSelf requirement, we need define a generic method:
protocol Ordered { ... }func binarySearch(sortedKeys: [T], forKey k: T) -> Int { ...} The differences between usingSelf requirements and not using them are far reaching. In particular, theSelf requirement puts us the in the static dispatch field and requires the use of generics and homogeneous collections. This is further illustrated in the picture below.

Retroactive modeling
To explore in more detail how protocols and structs can be used to replace a class hierarchy, Abrahams next introduces a Rendererplayground aimed at rendering geometrical figures. This sample allows to highlight the possibility of retroactive modeling that protocols and structs provide. In this concrete case, retroactive modeling is applied to create an extension ofCGContext that implements the requirements of aRenderer protocol:
protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)}extension CGContext : Renderer { ...}By doing this, theCGContext type can be used wherever aRenderer type is used, althoughCGContext was defined previously toRenderer.
On the other hand, it is possible to provide an autonomous implementation of the protocol through aTestRenderer class that outputs a textual representation of the geometrical figures:
struct TestRenderer : Renderer { func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") } func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") } ...}The twoRenderer implementation can be used interchangeably.
Protocol extensions
Swift 2.0 introduces a new feature that can make the use of protocols even more convenient: protocol extensions, which is a feature that allows to provide a default implementation for a protocol requirement. This is explained through the following code snippet:
protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func circleAt(center: CGPoint, radius: CGFloat) func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)}extension Renderer { func circleAt(center: CGPoint, radius: CGFloat) { arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi) }}In the context of ourRenderer example, having definecircleAt inside a protocol extension makes that implementation shared byCGContext andTestRenderer.
Constrained extensions
Protocol extensions allow the specification of constraints on used types. As an example, this allows for the definition of a protocol extension on aCollectionType only when the element of the collection satisfies a requirement:
extension CollectionType where Generator.Element : Equatable { public func indexOf(element: Generator.Element) -> Index? { for i in self.indices { if self[i] == element { return i } } return nil }}DeclaringGenerator.Element asEquatable allows to use the== operator inside ofindexOf.
The final part of the talk is dedicated to a few more tricks allowed by protocol extensions and constraints, such as beautifying generic function declarations, e.g. going from:
func binarySearch< C : CollectionType where C.Index == RandomAccessIndexType, C.Generator.Element : Ordered>(sortedKeys: C, forKey k: C.Generator.Element) -> Int { ... }To:
extension CollectionType where Index == RandomAccessIndexType, 2 Generator.Element : Ordered { ...}When are classes to be used?
To conclude his presentation, Abrahams notes that classes still have their place, in particular if youwant implicit sharing, e.g. when:
- Copying or comparing instances does not make sense.
- Instance lifetime is tied to some external effects, as with a TemporaryFile.
- Instances are “sinks” that only modify some external state, such as CGContext.
Furthermore, when using a framework like Cocoa, that is built around the idea of objects and subclassing, Abrahams says, it does not make sense trying to fight against the system. But, when refactoring a large class, using protocol and structs to factor out pieces of it can be much better.
Related Editorial
Demystifying Microservices for Jakarta EE & Java EE Developers
Related Sponsors
Related Sponsor
%2ffilters%3ano_upscale()%2fsponsorship%2ftopic%2f5aab5793-1aa2-43a6-9086-318627c6365a%2fPayaraLogo-1763716038782.png&f=jpg&w=240)
Move from complexity to control. Run and scale your Jakarta EE, Spring, and Quarkus applications on a unified platform that replaces infrastructure chaos with deployment simplicity and total autonomy.Learn More.
The InfoQ Newsletter
A round-up of last week’s content on InfoQ sent out every Tuesday. Join a community of over 250,000 senior developers.View an example