KEMBAR78
Protocol-Oriented Programming in Swift | PPTX
1
2
Protocol-oriented programming in Swift
Rostyslav Kobyzskyi
Software Engineer
16th Nov, 2016
3
1. Classes and problems
2. Start with Protocol
3. Using Protocols
4. Examples
5. Questions
Agenda
Dave Abrahams
• David Abrahams is a computer
programmer and author
• Abrahams became a member of
the C++ Standards Committee
in 1996
• In 2013 Abrahams became an
employee at Apple Inc, where
he is involved in the
development of the Swift
programming language
Why Protocols?
Classes
• Encapsulation
• Access Control
• Abstraction
• Namespace
• Expressive Syntax
• Extensibility
Classes
• Encapsulation
• Access Control
• Abstraction
• Namespace
• Expressive Syntax
• Extensibility
1. Implicit Sharing
A
B
Data
1. Implicit Sharing
A
B
Data
1. Implicit Sharing
A
B
Data
1. Implicit Sharing
A
B
Data
1. Implicit Sharing
A
B
Ponies
1. Implicit Sharing
B
Ponies
1. Implicit Sharing
B
Ponies
$@#$%?
1. Implicit Sharing
• Defensive Copy
• Inefficiency
• Race Condition
• Locks
• More Inefficiency
• Deadlock
• Complexity
• Bugs!
Values don’t Share.
That’s a good thing
Classes? They overshare…
2. Inheritance
Subclass
Stored
Properties
Superclass
2. Inheritance
One superclass
Subclass
Stored
Properties
Superclass
Instance
2. Inheritance
One superclass
Single Inheritance
Subclass
Stored
Properties
Superclass
Instance
2. Inheritance
One superclass
Single Inheritance
Super class may have Stored Properties
Subclass
Stored
Properties
Superclass
Stored
Properties
Instance
2. Inheritance
One superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
Subclass
Stored
Properties
Superclass
Stored
Properties
2. Inheritance
One superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
• Initialization problem
Instance
Subclass
Stored
Properties
Superclass
Stored
Properties
2. Inheritance
One superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
• Initialization problem
• Don’t break superclass invariants!
Instance
Subclass
Stored
Properties
Superclass
Stored
Properties
2. Inheritance
One superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
• Initialization problem
• Don’t break superclass invariants!
Know what/how to override (and when not)
Instance
Subclass
Stored
Properties
Superclass
Stored
Properties
3. Lost Type Relationships
class Ordered {
func precedes(other: Ordered) -> Bool
}
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0, 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
}
3. Lost Type Relationships
class Ordered {
func precedes(other: Ordered) -> Bool { }
}
3. Lost Type Relationships
class Ordered {
func precedes(other: Ordered) -> Bool
}
{ fatalError(“implement me!”) }
3. Lost Type Relationships
class Ordered {
func precedes(other: Ordered) -> Bool
}
{ fatalError(“implement me!”) } X
3. Lost Type Relationships
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
{ fatalError(“implement me!”) } X
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
3. Lost Type Relationships
{ fatalError(“implement me!”) } X
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
3. Lost Type Relationships
{ fatalError(“implement me!”) } X
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
3. Lost Type Relationships
{ fatalError(“implement me!”) } X
`Ordered` does not have a member named `value`
`Ordered` does not have a member named `value`
3. Lost Type Relationships
{ fatalError(“implement me!”) } X
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Label: Ordered { var text: String = “” ... }
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Label: Ordered { var text: String = “” ... }
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
3. Lost Type Relationships
{ fatalError(“implement me!”) } X
A better Abstraction
Mechanism
• Support value types (and classes)
• Support static type relationships (and dynamic dispatch)
• Non-monolithic
• Doesn’t impose instance data on models
• Doesn’t impose initialization burdens on models
• Makes clear what to implement
Swift is Protocol-Oriented
Programming Language
Start with a Protocol
Starting override with Protocols
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < other.value
}
}
{ fatalError(“implement me!”) } X
Starting override with Protocols
class Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
{ fatalError(“implement me!”) } X
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
{ fatalError(“implement me!”) } X
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
{ fatalError(“implement me!”) }
Protocol methods may not have bodies
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
Method does not override any method
from superclass
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
class Number: Ordered {
var value: Double = 0
override fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value}
}
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
struct Number: Ordered {
var value: Double = 0
fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
struct Number: Ordered {
var value: Double = 0
fund precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
struct Number: Ordered {
var value: Double = 0
fund precedes(other: Number) -> Bool {
return value < other.value
}
}
Starting override with Protocols
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
struct Number: Ordered {
var value: Double = 0
fund precedes(other: Number) -> Bool {
return value < other.value
}
}
Protocol requires function ‘precedes’ with type ‘(Oredered)’ -> Bool
candidate has non-matching type (`Number`) -> Bool
Starting override with Protocols
protocol Ordered {
func precedes(other: Self) -> Bool
}
struct Number: Ordered {
var value: Double = 0
fund precedes(other: Number) -> Bool {
return value < other.value
}
}
Self requirement
Using out Protocol
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0, 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
}
Using out Protocol
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0, 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
}
Protocol ‘Ordered’ can only be used as a generic constraint
because if has Self or associated type requirements
Using out Protocol
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0, 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
}
Protocol ‘Ordered’ can only be used as a generic constraint
because if has Self or associated type requirements
Using out Protocol
func binarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int {
var lo = 0, 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
}
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool func precedes(other: Self) -> Bool
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool
Usable as a type
sort(inout a: [Ordered])
func precedes(other: Self) -> Bool
Only usable as a generic constraint
sort<T: Ordered>(inout a: [T])
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool
Usable as a type
sort(inout a: [Ordered])
Every model must deal with all others
func precedes(other: Self) -> Bool
Only usable as a generic constraint
sort<T: Ordered>(inout a: [T])
Models are free from interaction
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool
Usable as a type
sort(inout a: [Ordered])
Every model must deal with all others
Dynamic dispatch
func precedes(other: Self) -> Bool
Only usable as a generic constraint
sort<T: Ordered>(inout a: [T])
Models are free from interaction
Static dispatch
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool
Usable as a type
sort(inout a: [Ordered])
Every model must deal with all others
Dynamic dispatch
Less optimizable
func precedes(other: Self) -> Bool
Only usable as a generic constraint
sort<T: Ordered>(inout a: [T])
Models are free from interaction
Static dispatch
More optimizable
A Primitive “Render”
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Drawable
protocol Drawable {
func draw(randerer: Renderer)
}
Polygon
protocol Drawable {
func draw(randerer: Renderer)
}
struct Polygon: Drawable {
func draw(renderer: Renderer) {
renderer.moveTo(corners.last!)
for p in corners {
renderer.lineTo(p)
}
}
var corners: [CGPoint] = []
}
Circle
protocol Drawable {
func draw(randerer: Renderer)
}
struct Circle: Drawable {
func draw(renderer: Renderer) {
renderer.artAt(center, radius: radius,
startAngle: 0.0, endAngle: twoPi)
}
}
var center: CGPoint
var radius: CGFloat
}
Diagram
protocol Drawable {
func draw(randerer: Renderer)
}
struct Diagram: Drawable {
func draw(renderer: Renderer) {
for f in elements {
f.draw(renderer)
}
}
var elements: [Drawable] = []
}
Test It!
var circle = Circle(
center: CGPoint(x: 187.5, y: 333.5),
radius: 93.75
)
Test It!
var circle = Circle(
center: CGPoint(x: 187.5, y: 333.5),
radius: 93.75
)
var triangle = Polygon(corners: [
CGPoint(x: 187.5, y: 427.25),
CGPoint(x: 268.69, y: 286.625),
CGPoint(x: 106.31, y: 286.625)
])
Test It!
var circle = Circle(
center: CGPoint(x: 187.5, y: 333.5),
radius: 93.75
)
var triangle = Polygon(corners: [
CGPoint(x: 187.5, y: 427.25),
CGPoint(x: 268.69, y: 286.625),
CGPoint(x: 106.31, y: 286.625)
])
var diagram = Diagram(elements: [circle: triangle])
Test It!
var circle = Circle(
center: CGPoint(x: 187.5, y: 333.5),
radius: 93.75
)
var triangle = Polygon(corners: [
CGPoint(x: 187.5, y: 427.25),
CGPoint(x: 268.69, y: 286.625),
CGPoint(x: 106.31, y: 286.625)
])
var diagram = Diagram(elements: [circle: triangle])
diagram.draw(Renderer())
Renderer as Protocol
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Renderer as Protocol
protocol Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Renderer as Protocol
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Renderer as Protocol
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
struct Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Renderer as Protocol
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
struct TestRenderer: Renderer {
func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) }
func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
print(“arcAt((center), radius: (radius)),” +
+ “ startAngle: (startAngle), endAngle: (endAngle)”)
}
}
Rendering with CoreGraphics
Retroactive modeling
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
Rendering with CoreGraphics
Retroactive modeling
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
Rendering with CoreGraphics
Retroactive modeling
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
extension CGContext: Renderer {
func moveTo(p: CGPoint) { }
func lineTo(p: CGPoint) { }
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) { }
}
Rendering with CoreGraphics
Retroactive modeling
extension CGContext: Renderer {
func moveTo(p: CGPoint) {
CGContextMoveToPoint(self, p.x, p.y)
}
func lineTo(p: CGPoint) {
CGContextAddLineToPoint(self, p.x, p.y)
}
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat) {
let arc = CGPathCreateMutable()
CGPathAddArc(arc, nil, center.x, center.y, radius,
startAngle, endAngle, true)
CGContextAddPath(self, arc)
}
}
Protocols and Generics for Testability
So much better than mocks
struct Bubble: Drawable {
func draw(r: Renderer) {
r.arcAt(center, radius, startAngle: 0, endAngle: twoPi)
r.arcAt(highlightCenter, radius: highlightRadius,
startAngle: 0, endAngle: twoPi
)
}
}
Protocols and Generics for Testability
So much better than mocks
struct Bubble: Drawable {
func draw(r: Renderer) {
r.arcAt(center, radius, startAngle: 0, endAngle: twoPi)
r.arcAt(highlightCenter, radius: highlightRadius,
startAngle: 0, endAngle: twoPi
)
}
}
struct Circle: Drawable {
func draw(r: Renderer) {
r.arcAt(center, radius, startAngle: 0, endAngle: twoPi)
}
}
Protocols and Generics for Testability
So much better than mocks
struct Bubble: Drawable {
func draw(r: Renderer) {
r.circleAt(center, radius)
r.arcAt(highlightCenter, radius: highlightRadius)
}
}
struct Circle: Drawable {
func draw(r: Renderer) {
r.circleAt(center, radius)
}
}
Adding a Circle Primitive
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(
center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat)
)
}
Adding a Circle Primitive
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(
center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat)
)
}
Adding a Circle Primitive
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)
)
}
Adding a Circle Primitive
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)
)
}
New requirement
Adding a Circle Primitive
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 TestRenderer {
func circleAt(center: CGPoint, radius: CGFloat) {
arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
}
}
Adding a Circle Primitive
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 CGContext {
func circleAt(center: CGPoint, radius: CGFloat) {
arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
}
}
Protocol Extension
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)
}
}
New!
Protocol Extension
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)
}
}
Shared Implementation
Protocol Extension
extension Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
extension TestRenderer: Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
Protocol Extension
extension Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
extension TestRenderer: Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
let r = TestRenderer()
r.circleAt(origin, radius: 1)
r.rectangleAt(edges)
Protocol Extension
extension Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
extension TestRenderer: Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
let r = TestRenderer()
r.circleAt(origin, radius: 1)
r.rectangleAt(edges)
Protocol Extension
customization point
extension Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
extension TestRenderer: Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectagleAt(edges: CGRect) { ... }
}
let r: Renderer = TestRenderer()
r.circleAt(origin, radius: 1)
r.rectangleAt(edges)
91
Questions
92
Thank you
Rostyslav
Software Engineer
rostyslav.kobyzskyi@globallogic.com
+38-093-254-3392

Protocol-Oriented Programming in Swift

  • 1.
  • 2.
    2 Protocol-oriented programming inSwift Rostyslav Kobyzskyi Software Engineer 16th Nov, 2016
  • 3.
    3 1. Classes andproblems 2. Start with Protocol 3. Using Protocols 4. Examples 5. Questions Agenda
  • 4.
    Dave Abrahams • DavidAbrahams is a computer programmer and author • Abrahams became a member of the C++ Standards Committee in 1996 • In 2013 Abrahams became an employee at Apple Inc, where he is involved in the development of the Swift programming language
  • 5.
  • 6.
    Classes • Encapsulation • AccessControl • Abstraction • Namespace • Expressive Syntax • Extensibility
  • 7.
    Classes • Encapsulation • AccessControl • Abstraction • Namespace • Expressive Syntax • Extensibility
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
    1. Implicit Sharing •Defensive Copy • Inefficiency • Race Condition • Locks • More Inefficiency • Deadlock • Complexity • Bugs!
  • 16.
    Values don’t Share. That’sa good thing Classes? They overshare…
  • 17.
  • 18.
  • 19.
    Instance 2. Inheritance One superclass SingleInheritance Subclass Stored Properties Superclass
  • 20.
    Instance 2. Inheritance One superclass SingleInheritance Super class may have Stored Properties Subclass Stored Properties Superclass Stored Properties
  • 21.
    Instance 2. Inheritance One superclass SingleInheritance Super class may have Stored Properties • You must accept them Subclass Stored Properties Superclass Stored Properties
  • 22.
    2. Inheritance One superclass SingleInheritance Super class may have Stored Properties • You must accept them • Initialization problem Instance Subclass Stored Properties Superclass Stored Properties
  • 23.
    2. Inheritance One superclass SingleInheritance Super class may have Stored Properties • You must accept them • Initialization problem • Don’t break superclass invariants! Instance Subclass Stored Properties Superclass Stored Properties
  • 24.
    2. Inheritance One superclass SingleInheritance Super class may have Stored Properties • You must accept them • Initialization problem • Don’t break superclass invariants! Know what/how to override (and when not) Instance Subclass Stored Properties Superclass Stored Properties
  • 25.
    3. Lost TypeRelationships class Ordered { func precedes(other: Ordered) -> Bool } func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { var lo = 0, 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 }
  • 26.
    3. Lost TypeRelationships class Ordered { func precedes(other: Ordered) -> Bool { } }
  • 27.
    3. Lost TypeRelationships class Ordered { func precedes(other: Ordered) -> Bool } { fatalError(“implement me!”) }
  • 28.
    3. Lost TypeRelationships class Ordered { func precedes(other: Ordered) -> Bool } { fatalError(“implement me!”) } X
  • 29.
    3. Lost TypeRelationships class Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } } { fatalError(“implement me!”) } X
  • 30.
    class Ordered { funcprecedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } } 3. Lost Type Relationships { fatalError(“implement me!”) } X
  • 31.
    class Ordered { funcprecedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } } 3. Lost Type Relationships { fatalError(“implement me!”) } X
  • 32.
    class Ordered { funcprecedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } } 3. Lost Type Relationships { fatalError(“implement me!”) } X `Ordered` does not have a member named `value`
  • 33.
    `Ordered` does nothave a member named `value` 3. Lost Type Relationships { fatalError(“implement me!”) } X class Ordered { func precedes(other: Ordered) -> Bool } class Label: Ordered { var text: String = “” ... } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } }
  • 34.
    class Ordered { funcprecedes(other: Ordered) -> Bool } class Label: Ordered { var text: String = “” ... } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } } 3. Lost Type Relationships { fatalError(“implement me!”) } X
  • 35.
    A better Abstraction Mechanism •Support value types (and classes) • Support static type relationships (and dynamic dispatch) • Non-monolithic • Doesn’t impose instance data on models • Doesn’t impose initialization burdens on models • Makes clear what to implement
  • 36.
  • 37.
    Start with aProtocol
  • 38.
    Starting override withProtocols class Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < other.value } } { fatalError(“implement me!”) } X
  • 39.
    Starting override withProtocols class Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } } { fatalError(“implement me!”) } X
  • 40.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } } { fatalError(“implement me!”) } X
  • 41.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } } { fatalError(“implement me!”) } Protocol methods may not have bodies
  • 42.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } } Method does not override any method from superclass
  • 43.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } class Number: Ordered { var value: Double = 0 override fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value} }
  • 44.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } struct Number: Ordered { var value: Double = 0 fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } }
  • 45.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } struct Number: Ordered { var value: Double = 0 fund precedes(other: Ordered) -> Bool { return value < (other as! Number).value } }
  • 46.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } struct Number: Ordered { var value: Double = 0 fund precedes(other: Number) -> Bool { return value < other.value } }
  • 47.
    Starting override withProtocols protocol Ordered { func precedes(other: Ordered) -> Bool } struct Number: Ordered { var value: Double = 0 fund precedes(other: Number) -> Bool { return value < other.value } } Protocol requires function ‘precedes’ with type ‘(Oredered)’ -> Bool candidate has non-matching type (`Number`) -> Bool
  • 48.
    Starting override withProtocols protocol Ordered { func precedes(other: Self) -> Bool } struct Number: Ordered { var value: Double = 0 fund precedes(other: Number) -> Bool { return value < other.value } } Self requirement
  • 49.
    Using out Protocol funcbinarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { var lo = 0, 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 }
  • 50.
    Using out Protocol funcbinarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { var lo = 0, 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 } Protocol ‘Ordered’ can only be used as a generic constraint because if has Self or associated type requirements
  • 51.
    Using out Protocol funcbinarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int { var lo = 0, 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 } Protocol ‘Ordered’ can only be used as a generic constraint because if has Self or associated type requirements
  • 52.
    Using out Protocol funcbinarySearch<T: Ordered>(sortedKeys: [T], forKey k: T) -> Int { var lo = 0, 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 }
  • 53.
    Two-world of Protocols WithoutSelf Requirement With Self Requirement func precedes(other: Ordered) -> Bool func precedes(other: Self) -> Bool
  • 54.
    Two-world of Protocols WithoutSelf Requirement With Self Requirement func precedes(other: Ordered) -> Bool Usable as a type sort(inout a: [Ordered]) func precedes(other: Self) -> Bool Only usable as a generic constraint sort<T: Ordered>(inout a: [T])
  • 55.
    Two-world of Protocols WithoutSelf Requirement With Self Requirement func precedes(other: Ordered) -> Bool Usable as a type sort(inout a: [Ordered]) Every model must deal with all others func precedes(other: Self) -> Bool Only usable as a generic constraint sort<T: Ordered>(inout a: [T]) Models are free from interaction
  • 56.
    Two-world of Protocols WithoutSelf Requirement With Self Requirement func precedes(other: Ordered) -> Bool Usable as a type sort(inout a: [Ordered]) Every model must deal with all others Dynamic dispatch func precedes(other: Self) -> Bool Only usable as a generic constraint sort<T: Ordered>(inout a: [T]) Models are free from interaction Static dispatch
  • 57.
    Two-world of Protocols WithoutSelf Requirement With Self Requirement func precedes(other: Ordered) -> Bool Usable as a type sort(inout a: [Ordered]) Every model must deal with all others Dynamic dispatch Less optimizable func precedes(other: Self) -> Bool Only usable as a generic constraint sort<T: Ordered>(inout a: [T]) Models are free from interaction Static dispatch More optimizable
  • 58.
    A Primitive “Render” structRenderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 59.
    Drawable protocol Drawable { funcdraw(randerer: Renderer) }
  • 60.
    Polygon protocol Drawable { funcdraw(randerer: Renderer) } struct Polygon: Drawable { func draw(renderer: Renderer) { renderer.moveTo(corners.last!) for p in corners { renderer.lineTo(p) } } var corners: [CGPoint] = [] }
  • 61.
    Circle protocol Drawable { funcdraw(randerer: Renderer) } struct Circle: Drawable { func draw(renderer: Renderer) { renderer.artAt(center, radius: radius, startAngle: 0.0, endAngle: twoPi) } } var center: CGPoint var radius: CGFloat }
  • 62.
    Diagram protocol Drawable { funcdraw(randerer: Renderer) } struct Diagram: Drawable { func draw(renderer: Renderer) { for f in elements { f.draw(renderer) } } var elements: [Drawable] = [] }
  • 63.
    Test It! var circle= Circle( center: CGPoint(x: 187.5, y: 333.5), radius: 93.75 )
  • 64.
    Test It! var circle= Circle( center: CGPoint(x: 187.5, y: 333.5), radius: 93.75 ) var triangle = Polygon(corners: [ CGPoint(x: 187.5, y: 427.25), CGPoint(x: 268.69, y: 286.625), CGPoint(x: 106.31, y: 286.625) ])
  • 65.
    Test It! var circle= Circle( center: CGPoint(x: 187.5, y: 333.5), radius: 93.75 ) var triangle = Polygon(corners: [ CGPoint(x: 187.5, y: 427.25), CGPoint(x: 268.69, y: 286.625), CGPoint(x: 106.31, y: 286.625) ]) var diagram = Diagram(elements: [circle: triangle])
  • 66.
    Test It! var circle= Circle( center: CGPoint(x: 187.5, y: 333.5), radius: 93.75 ) var triangle = Polygon(corners: [ CGPoint(x: 187.5, y: 427.25), CGPoint(x: 268.69, y: 286.625), CGPoint(x: 106.31, y: 286.625) ]) var diagram = Diagram(elements: [circle: triangle]) diagram.draw(Renderer())
  • 67.
    Renderer as Protocol structRenderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } } struct Renderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 68.
    Renderer as Protocol protocolRenderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } } struct Renderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 69.
    Renderer as Protocol protocolRenderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) } struct Renderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 70.
    Renderer as Protocol protocolRenderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) } struct Renderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 71.
    Renderer as Protocol protocolRenderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) } struct TestRenderer: Renderer { func moveTo(p: CGPoint) { print(“moveTo((p.x), (p.y))”) } func lineTo(p: CGPoint) { print(“lineTo((p.x), (p.y))”) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { print(“arcAt((center), radius: (radius)),” + + “ startAngle: (startAngle), endAngle: (endAngle)”) } }
  • 72.
    Rendering with CoreGraphics Retroactivemodeling protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) }
  • 73.
    Rendering with CoreGraphics Retroactivemodeling protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) }
  • 74.
    Rendering with CoreGraphics Retroactivemodeling protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) } extension CGContext: Renderer { func moveTo(p: CGPoint) { } func lineTo(p: CGPoint) { } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { } }
  • 75.
    Rendering with CoreGraphics Retroactivemodeling extension CGContext: Renderer { func moveTo(p: CGPoint) { CGContextMoveToPoint(self, p.x, p.y) } func lineTo(p: CGPoint) { CGContextAddLineToPoint(self, p.x, p.y) } func arcAt(center: CGPoint, radius: CGFloat, startAngler: CGFloat, endAngle: CGFloat) { let arc = CGPathCreateMutable() CGPathAddArc(arc, nil, center.x, center.y, radius, startAngle, endAngle, true) CGContextAddPath(self, arc) } }
  • 76.
    Protocols and Genericsfor Testability So much better than mocks struct Bubble: Drawable { func draw(r: Renderer) { r.arcAt(center, radius, startAngle: 0, endAngle: twoPi) r.arcAt(highlightCenter, radius: highlightRadius, startAngle: 0, endAngle: twoPi ) } }
  • 77.
    Protocols and Genericsfor Testability So much better than mocks struct Bubble: Drawable { func draw(r: Renderer) { r.arcAt(center, radius, startAngle: 0, endAngle: twoPi) r.arcAt(highlightCenter, radius: highlightRadius, startAngle: 0, endAngle: twoPi ) } } struct Circle: Drawable { func draw(r: Renderer) { r.arcAt(center, radius, startAngle: 0, endAngle: twoPi) } }
  • 78.
    Protocols and Genericsfor Testability So much better than mocks struct Bubble: Drawable { func draw(r: Renderer) { r.circleAt(center, radius) r.arcAt(highlightCenter, radius: highlightRadius) } } struct Circle: Drawable { func draw(r: Renderer) { r.circleAt(center, radius) } }
  • 79.
    Adding a CirclePrimitive protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt( center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) ) }
  • 80.
    Adding a CirclePrimitive protocol Renderer { func moveTo(p: CGPoint) func lineTo(p: CGPoint) func arcAt( center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) ) }
  • 81.
    Adding a CirclePrimitive 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) ) }
  • 82.
    Adding a CirclePrimitive 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) ) } New requirement
  • 83.
    Adding a CirclePrimitive 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 TestRenderer { func circleAt(center: CGPoint, radius: CGFloat) { arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi) } }
  • 84.
    Adding a CirclePrimitive 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 CGContext { func circleAt(center: CGPoint, radius: CGFloat) { arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi) } }
  • 85.
    Protocol Extension 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) } } New!
  • 86.
    Protocol Extension 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) } } Shared Implementation
  • 87.
    Protocol Extension extension Renderer{ func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } extension TestRenderer: Renderer { func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } }
  • 88.
    Protocol Extension extension Renderer{ func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } extension TestRenderer: Renderer { func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } let r = TestRenderer() r.circleAt(origin, radius: 1) r.rectangleAt(edges)
  • 89.
    Protocol Extension extension Renderer{ func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } extension TestRenderer: Renderer { func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } let r = TestRenderer() r.circleAt(origin, radius: 1) r.rectangleAt(edges)
  • 90.
    Protocol Extension customization point extensionRenderer { func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } extension TestRenderer: Renderer { func circleAt(center: CGPoint, radius: CGFloat) { ... } func rectagleAt(edges: CGRect) { ... } } let r: Renderer = TestRenderer() r.circleAt(origin, radius: 1) r.rectangleAt(edges)
  • 91.
  • 92.