Protocol-Oriented Programming in Swift
Oleksandr Stepanov
Oleksandr Stepanov iOS developer @ AKQA Inc.
We are hiring!
Agenda
Agenda
• Protocols in Swift vs Objective-C
Agenda
• Protocols in Swift vs Objective-C
• Protocol-oriented programming
‣ What is that? ‣ Why to use it? ‣ How to use it?
Agenda
• Protocols in Swift vs Objective-C
• Protocol-oriented programming
‣ What is that? ‣ Why to use it? ‣ How to use it?
• Examples
Protocols
protocol someProtocol {
}
protocol someProtocol { var someInt: Int { get } var someString: String? { get set }}
protocol someProtocol { var someInt: Int { get } var someString: String? { get set }
func doSomething(inputA: Int) -> String?}
protocol someProtocol { associatedtype TypeA;
var someInt: Int { get } var someString: String? { get set }
func doSomething(inputA: Int) -> String? func doSomethingWithTypeA(typeA: TypeA)}
protocol someProtocol { associatedtype TypeA;
var someInt: Int { get } var someString: String? { get set }
func doSomething(inputA: Int) -> String? func doSomethingWithTypeA(typeA: TypeA)}
protocol successorProtocol: someProtocol {
}
…
extension somethingProtocol { func doSomethingDifferent() { print("Oh man, that is different") } func doSomethingWithTypeA(typeA: TypeA) { print("typeA: \(typeA)") }}
ProtocolsSwift Objective-C
Protocol Inheritance ✅ ❌Protocol extensions ✅ ❌
Default implementation ✅ ❌Associated Types (aka Generics) ✅ ❌
Available in structures and enums as well ✅ ❌
Swift Objective-C
Protocol Inheritance ✅ ❌Protocol extensions ✅ ❌
Default implementation ✅ ❌Associated Types (aka Generics) ✅ ❌
Available in structures and enums as well ✅ ❌
Optional methods ❌ ✅
Protocols
import Foundation
@objc protocol optionalProtocol {
@objc optional func someOptionalMethod() @objc optional var someOptionalInt: Int { get set }
}
Swift Objective-C
Protocol Inheritance ✅ ❌Protocol extensions ✅ ❌
Default implementation ✅ ❌Associated Types (aka Generics) ✅ ❌
Available in structures and enums as well ✅ ❌
Optional methods ✅ (@objc) ✅
Protocols
Protocol-oriented programming
🤔
Protocol-oriented programming
What is this?
🤔
WWDC 2015 - Session 408
Protocol-Oriented Programming in Swift
“Swift is a protocol-oriented programming language.”
Dave Abrahams
WWDC 2015 - Session 408
Protocol-Oriented Programming in Swift
“Swift is a protocol-oriented programming language.”
Dave Abrahams
https://developer.apple.com/videos/play/wwdc2015/408/
https://developer.apple.com/videos/play/wwdc2016/419/
Cars project 🚗
Implementation Inheritance approach
class Car {let wheels = 4
func make() {print("🚗 is built")
} }
class Car {let wheels = 4
func make() {print("🚗 is built")
} } class CarFactory { var model: Car?
func makeACar() { model?.make() }}
…class Sedan: Car {}
let sedan = Sedan()
let carFactory = CarFactory()carFactory.model = sedancarFactory.makeACar() // prints “🚗 is built”
Let’s make 🏎
…class Bolide: Car {
override func make() { print("🏎 is built")
} }
let bolide = Bolide()let bolideFactory = CarFactory()bolideFactory.model = bolidebolideFactory.makeACar() // prints "🏎 is built" Page 1 of 1
Let’s make 🏍
Implementation Inheritance problems
class CarFactory { var model: Car?
func makeACar() { model?.make() }}
Tight coupling
Hard to maintain
Inheritance hierarchies
Hard to extend
class Car {let wheels = 4
func make() {print("🚗 is built")
} } class Sedan: Car {}
Implementation is a white-box
… even using proper encapsulation
class Car {let wheels = 4
func make() {print("🚗 is built")
} } class Sedan: Car {}
Protocol-oriented programming approach
✦ Separation of public interface from the implementation
✦ Separation of public interface from the implementation
✦ Software defined in components that talk to each other using interfaces
✦ Separation of public interface from the implementation
✦ Software defined in components that talk to each other using interfaces
✦ May be used in conjunction with classes, structs and enums.
protocol Vehicle {func make()
}
protocol WheelsVehicle { var wheels: Int { get }}
…protocol FourWheelsVehicle: WheelsVehicle {}
extension FourWheelsVehicle { var wheels: Int {
get { return 4
} }
}
…struct Car: Vehicle, FourWheelsVehicle { func make() {
print("🚗 is built") } }
}
struct Bolide: Vehicle, FourWheelsVehicle {func make() {
print("🏎 is built") }
}
…struct Car: Vehicle, FourWheelsVehicle { func make() {
print("🚗 is built") } }
}
struct Bolide: Vehicle, FourWheelsVehicle {func make() {
print("🏎 is built") }
}
…struct Car: Vehicle, FourWheelsVehicle { func make() {
print("🚗 is built") } }
}
struct Bolide: Vehicle, FourWheelsVehicle {func make() {
print("🏎 is built") }
}
…protocol TwoWheelsVehicle: WheelsVehicle {}
extension TwoWheelsVehicle { var wheels: Int { get { return 2 } } }
struct MotorBike: Vehicle, TwoWheelsVehicle { func make() {
print("🏍 is built") } }
}
…protocol VehicleFactory { var model: Vehicle? { get set }
func makeACar()}
extension VehicleFactory { func makeACar() { model?.make() }}
…extension VehicleFactory { func repairACar() {
// ... }
}
…class CarFactory: VehicleFactory { var model: Vehicle?}
let bolide = Bolide()let carFactory = CarFactory()
carFactory.model = bolidecarFactory.makeACar() // prints "🏎 is built"
✦ Reusability
✦ Reusability
✦ Extensibility
✦ Reusability
✦ Extensibility
✦ Black-boxed
✦ Reusability
✦ Extensibility
✦ Black-boxed
MORE MAINTAINABLE
Patterns where it can be useful
✦ Data types• Abstraction• Mock objects for test
✦ Data types• Abstraction• Mock objects for test
✦ Dependency injection
✦ Data types• Abstraction• Mock objects for test
✦ Dependency injection
✦ Detached architecture• MVVM
✦ Data types• Abstraction• Mock objects for test
✦ Dependency injection
✦ Detached architecture• MVVM
✦ Testing• Unit• A/B testing interfaces
Real life example
Real life exampleUIKit
Table view cell
import UIKit struct Event {}
class EventTableViewCell: UITableViewCell {}
import UIKit struct Event { let icon: UIImage? let title: String let date: Date}
class EventTableViewCell: UITableViewCell { @IBOutlet var iconView: UIImageView! @IBOutlet var titleLabel: UILabel! @IBOutlet var dateLabel: UILabel!}
import UIKit struct Event { let icon: UIImage? let title: String let date: Date}
class EventTableViewCell: UITableViewCell { @IBOutlet var iconView: UIImageView! @IBOutlet var titleLabel: UILabel! @IBOutlet var dateLabel: UILabel!
func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description
} }
Collection view cell
…class EventTableViewCell: UITableViewCell {
… func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description
} }
class EventCollectionViewCell: UICollectionViewCell {…
func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description
} }
Header view
…class EventTableViewCell: UITableViewCell { … func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description
} }class EventCollectionViewCell: UICollectionViewCell {
… func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description
} }class EventHeaderView: UIView {
… func set(event: Event) { iconView.image = event.icon titleLabel.text = event.title dateLabel.text = event.date.description} }
Maintenance
Maintenance
😢
POP approach
protocol EventViewProtocol { var iconView: UIImageView! { get set } var titleLabel: UILabel! { get set } var dateLabel: UILabel! { get set }
func set(event: Event)}
protocol EventViewProtocol { var iconView: UIImageView! { get set } var titleLabel: UILabel! { get set } var dateLabel: UILabel! { get set }
func set(event: Event)}
extension EventViewProtocol { func set(event: Event) {
iconView.image = event.icontitleLabel.text = event.titledateLabel.text = event.date.description
}}
…class EventTableViewCell: UITableViewCell { @IBOutlet var iconView: UIImageView! @IBOutlet var titleLabel: UILabel! @IBOutlet var dateLabel: UILabel!}
extension EventTableViewCell: EventViewProtocol{}
Test it!
🔨
…import XCTest
class TestView { var iconView: UIImageView! = UIImageView() var titleLabel: UILabel! = UILabel() var dateLabel: UILabel! = UILabel()}
extension TestView: EventViewProtocol {}
…let eventDate = Date.init()let event = Event.init(icon: UIImage.init(named: “testEventIcon”)
title: "event title", date: eventDate)
let testView = TestView()testView.set(event: event)
…let eventDate = Date.init()let event = Event.init(icon: UIImage.init(named: “testEventIcon”)
title: "event title", date: eventDate)
let testView = TestView()testView.set(event: event)
XCTAssertEqual ( testView.iconView.image, UIImage.init(named:”testEventIcon”) )
XCTAssertEqual ( testView.titleLabel.text, "event title” )
XCTAssertEqual ( testView.dateLabel.text, eventDate.description )
✦ No code duplication -> better maintainable
✦ No code duplication -> better maintainable
✦ Better testable
✦ No code duplication -> better maintainable
✦ Better testable
PROFIT!
One more sample POP + Enums
One more sample POP + Enums
NSNotification
- Literal string names
- Literal string names
- Potential for mismatched strings
- Literal string names
- Potential for mismatched strings
- There must be a better approach
- Slightly better …
- Slightly better …
- … but can we do even better?
}
}
}
How to enhance it with POP ?
}
❌
✦ No mismatched strings
✦ No mismatched strings
✦Simpler to read and maintain
✦ No mismatched strings
✦Simpler to read and maintain
✦ Notification handlers may be classes, structs and enums
✦ No mismatched strings
✦Simpler to read and maintain
✦ Notification handlers may be classes, structs and enums
✦ … the same for notification type
reusability
reusability
extensibility
understandabilityreusability
extensibility
maintainability
understandabilityreusability
extensibility
maintainability
understandability
testability
reusability
extensibility
POP
maintainability
understandability
testability
reusability
extensibility
it’s not a
it’s not a
treat it carefully
Thank you!
Q&A