Protocol-Oriented Programming in Swift
-
Upload
globallogic-ukraine -
Category
Software
-
view
533 -
download
1
Transcript of Protocol-Oriented Programming in Swift
1
2
Protocol-oriented programming in SwiftRostyslav KobyzskyiSoftware Engineer16th Nov, 2016
3
1. Classes and problems2. Start with Protocol3. Using Protocols4. Examples5. 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 thingClasses? They overshare…
2. Inheritance
SubclassStoredProperties
Superclass
2. InheritanceOne superclass
SubclassStoredProperties
Superclass
Instance
2. InheritanceOne superclass
Single Inheritance
SubclassStoredProperties
Superclass
Instance
2. InheritanceOne superclass
Single Inheritance
Super class may have Stored Properties
SubclassStoredProperties
SuperclassStoredProperties
Instance
2. InheritanceOne superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
SubclassStoredProperties
SuperclassStoredProperties
2. InheritanceOne superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
• Initialization problem
Instance
SubclassStoredProperties
SuperclassStoredProperties
2. InheritanceOne superclass
Single Inheritance
Super class may have Stored Properties
• You must accept them
• Initialization problem
• Don’t break superclass invariants!
Instance
SubclassStoredProperties
SuperclassStoredProperties
2. InheritanceOne 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
SubclassStoredProperties
SuperclassStoredProperties
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!”) } Xclass 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)’ -> Boolcandidate 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 Protocolfunc 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 Protocolfunc 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 Protocolfunc 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 Protocolfunc 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 typesort(inout a: [Ordered])
func precedes(other: Self) -> Bool
Only usable as a generic constraintsort<T: Ordered>(inout a: [T])
Two-world of Protocols
Without Self Requirement With Self Requirement
func precedes(other: Ordered) -> Bool
Usable as a typesort(inout a: [Ordered])
Every model must deal with all others
func precedes(other: Self) -> Bool
Only usable as a generic constraintsort<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 typesort(inout a: [Ordered])
Every model must deal with all others
Dynamic dispatch
func precedes(other: Self) -> Bool
Only usable as a generic constraintsort<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 typesort(inout a: [Ordered])
Every model must deal with all others
Dynamic dispatch
Less optimizable
func precedes(other: Self) -> Bool
Only usable as a generic constraintsort<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)”)
}
}
Drawableprotocol Drawable {
func draw(randerer: Renderer)
}
Polygonprotocol 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] = []
}
Circleprotocol 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
}
Diagramprotocol 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 Protocolstruct 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 Protocolprotocol 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 Protocolprotocol 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 Protocolprotocol 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 Protocolprotocol 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 CoreGraphicsRetroactive modelingprotocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
Rendering with CoreGraphicsRetroactive modelingprotocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngler: CGFloat, endAngle: CGFloat)
}
Rendering with CoreGraphicsRetroactive modelingprotocol 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 CoreGraphicsRetroactive 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 TestabilitySo much better than mocksstruct 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 TestabilitySo much better than mocksstruct 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 TestabilitySo much better than mocksstruct 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 Primitiveprotocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(
center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat)
)
}
Adding a Circle Primitiveprotocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(
center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat)
)
}
Adding a Circle Primitiveprotocol 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 Primitiveprotocol 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 Primitiveprotocol 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 Primitiveprotocol 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 Extensionprotocol 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 Extensionprotocol 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 Extensionextension 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 Extensionextension 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 Extensionextension 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 Extensioncustomization pointextension 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