PATs IRL
@NatashaTheRobot
Protocol-Oriented Programming in Swift
Dave Abrahams Professor of Blowing-Your-Mind
–- Professor of Blowing-Your-Mind
"Swift is a Protocol-Oriented Programming Language"
Protocols with Associated Types
🤔
Protocols with Associated Types
🤗
• Models
• Storyboards
• Networking
PATs 💖 Models
protocol HasInit {
init()
}
class Pokemon<Power: HasInit> {
func attack() -> Power {
return Power()
}
}
// power types
struct 🌧: HasInit { }
struct 🌩: HasInit { }
struct 🔥: HasInit { }
class Pikachu: Pokemon<🌩> {}
class Vaporeon: Pokemon<🌧> {}
let pikachu = Pikachu()
pikachu.attack() // 🌩
let vaporeon = Vaporeon()
vaporeon.attack() // 🌧
Mixins and Traits in Swift 2.0
by Matthijs Hollemans
Mixins and Traits in Swift 2.0
by Matthijs Hollemans
protocol PowerTrait {
associatedtype Power: HasInit
func attack() -> Power
}
extension PowerTrait {
func attack() -> Power {
return Power()
}
}
struct Pikachu: PowerTrait {
associatedtype Power = 🌩
}
struct Vaporeon: PowerTrait {
// 🌧 is inferred as the associated type
func attack() -> 🌧 {
// custom attack logic
return 🌧()
}
}
PATs 💖 Storyboards
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
switch segueIdentifier(forSegue: segue) {
case .StatueOfLibertyAdventure:
let statueOfLibertyVC = segue.destinationViewController as?
StatueOfLiberityViewController
statueOfLibertyVC?.user = user
case .EmpireStateAdventure:
let empireStateVC = segue.destinationViewController as?
EmpireStateViewController
empireStateVC?.friends = [friend1, friend2]
protocol Injectable {
associatedtype Item
func inject(item: Item)
func assertDependencies()
}
class AdventureDetailViewController: UIViewController, Injectable {
private var user: User!
override func viewDidLoad() {
super.viewDidLoad()
assertDependencies()
// Do any additional setup after loading the view.
}
func inject(item: User) {
user = item
}
func assertDependencies() {
assert(user != nil)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
switch segueIdentifier(forSegue: segue) {
case .StatueOfLibertyAdventure:
let statueOfLibertyVC = segue.destinationViewController as?
StatueOfLiberityViewController
statueOfLibertyVC?.inject(item: user)
case .EmpireStateAdventure:
let empireStateVC = segue.destinationViewController as?
EmpireStateViewController
empireStateVC?.inject(item: [friend1, friend2])
PATs 💖 Networking
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
enum Result<T> {
case Success(T)
case Failure(ErrorType)
}
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
// NYCFoodViewController
var dataSource = [Food]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
getFood()
}
private func getFood() {
FoodService().get() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
View Controller Tests?!!! 😱
// NYCFoodViewController
private func getFood() {
FoodService().get() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
// NYCFoodViewController
func getFood(fromService service: FoodService) {
service.getFood() { [weak self] result in
// handle result
}
}
// NYCFoodViewControllerTests
func testFetchFood() {
viewController.getFood(fromService: FoodService())
// 🤔 now what?
}
struct FoodService {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
protocol Gettable {
func get(completionHandler: Result<[Food]> -> Void)
}
protocol Gettable {
associatedtype T
func get(completionHandler: Result<T> -> Void)
}
struct FoodService: Gettable {
func get(completionHandler: Result<[Food]> -> Void) {
// make asynchronous API call
// and return appropriate result
}
}
// NYCFoodViewController
override func viewDidLoad() {
super.viewDidLoad()
getFood(fromService: FoodService())
}
func getFood<S: Gettable where S.T == [Food]>(fromService service: S) {
service.get() { [weak self] result in
switch result {
case .Success(let food):
self?.dataSource = food
case .Failure(let error):
self?.showError(error)
}
}
}
// NYCFoodViewControllerTests
class Fake_FoodService: Gettable {
var getWasCalled = false
func get(completionHandler: Result<[Food]> -> Void) {
getWasCalled = true
completionHandler(Result.Success(food))
}
}
// NYCFoodViewControllerTests
func testFetchFood() {
let fakeFoodService = Fake_FoodService()
viewController.getFood(fromService: fakeFoodService)
XCTAssertTrue(fakeFoodService.getWasCalled)
XCTAssertEqual(viewController.dataSource.count, food.count)
XCTAssertEqual(viewController.dataSource, food)
}
T H A N K Y O U !
• @NatashaTheRobot
• @NatashaTheNomad
• This Week in Swift Newsletter
• @tryswiftnyc 🐥🗽🎉
• BROOKLYNSWIFT - 20% OFF
• Volunteers 🙋

Practical Protocols with Associated Types