REAL-WORLD PROTOCOLS
BEYOND CRUSTY
ANIMAL
DOG CAT
TIGER HOUSECATWOLF DOMESTIC
CHIHUAHUA GREAT DANE PERSIAN MANX
BIRD
protocol Animal {
func eat(food: Food)
}
protocol Animal {
typealias Food
func eat(food: Food)
}
// An Animal can eat
protocol Animal {
typealias Food
func eat(food: Food)
}
struct Cow: Animal {
func eat(food: Grass) { print("moo") }
}
struct Sheep: Animal {
func eat(food: Grass) { print("bah") }
}
let grassEaters: [Animal] = [Cow(), Sheep()]
error: protocol 'Animal' can only be used as a generic constraint because
it has Self or associated type requirements
Cow: Animal
eat()
AnyAnimal: Animal
eat()
Cow: Animal
eat()
let eat = Cow().eat
let eat = Cow().eat
eat(Grass())
let eat = { Cow().eat($0) }
eat(Grass())
struct AnyAnimal<Food>: Animal {
init<A: Animal where A.Food == Food>(_ animal: A) {
_eat = animal.eat
}
func eat(food: Food) { _eat(food) }
private let _eat: (Food) -> Void
}
let grassEaters = [AnyAnimal(Cow()), AnyAnimal(Sheep())]
for eater in grassEaters {
eater.eat(Grass())
}
protocol Animal {
typealias Food
func eat(food: Food)
}
struct AnyAnimal<Food>: Animal {
func eat(food: Food) { _eat(food) }
init ...
private let _eat: (Food) -> Void
}
struct Animal<Food> {
let eat: (Food) -> Void
}
protocol Animal {
typealias Food
func eat(food: Food)
}
let grassEaters = [
Animal(eat: cow.eat), // method
Animal(eat: { _ in print("bah") } ) // function literal
]
let session = NSURLSession()
session.delegate = self
let task = session.dataTaskWithURL(url)
let task = session.dataTaskWithURL(url,
completionHandler: { ... })
DELEGATES
HANDLERS
SOME RULES OF THUMB
‣ One method? Maybe function.
‣ Three or more methods? Protocol.
‣ Relationship short-lived? Function.
‣ Relationship ongoing? Probably protocol/delegate.
protocol P {
typealias A
typealias B
func x(a: A) -> B
func y(a: A) -> B
}
struct S<A,B> {
func x(a: A) -> B {…}
func y(a: A) -> B {…}
}
func x<A,B>(a: A) -> B {…}
func y<A,B>(a: A) -> B {…}
Pick the form that works 

best for your problem.
THE GENERIC SOLVER
PARSER SEARCHER OUTPUT
protocol Parsing {
typealias Problem
func parse(data: NSData) -> Problem
}
protocol Searching {
typealias Problem
typealias Solution
func search(problem: Problem) -> Solution
}
protocol Outputting {
typealias Solution
func output(solution: Solution)
}
struct Solver<
Parser: Parsing,
Searcher: Searching,
Outputter: Outputting
where
Parser.Problem == Searcher.Problem,
Searcher.Solution == Outputter.Solution
>{
let parser: Parser
let searcher: Searcher
let outputter: Outputter
func solve(data: NSData) {
outputter.output(searcher.search(parser.parse(data)))
}
}
protocol Solving {
typealias Parser: Parsing
typealias Searcher: Searching
typealias Outputter: Outputting
var parser: Parser { get }
var searcher: Searcher { get }
var outputter: Outputter { get }
}
extension Solving where
Parser.Problem == Searcher.Problem,
Searcher.Solution == Outputter.Solution
{
func solve(data: NSData) {
outputter.output(searcher.search(parser.parse(data)))
}
}
struct SimpleSolver<
Parser: Parsing,
Searcher: Searching,
Outputter: Outputting
where
Parser.Problem == Searcher.Problem,
Searcher.Solution == Outputter.Solution
>: Solving {
let parser: Parser
let searcher: Searcher
let outputter: Outputter
// ...
}
All the code repetition was making him
ornery, and if Crusty ain't happy, ain't
nobody happy.
Protocol-Oriented Programming in Swift
protocol Solving {
typealias Problem
typealias Solution
func parse(data: NSData) -> Problem
func search(problem: Problem) -> Solution
func output(solution: Solution) -> Void
}
extension Solving {
func solve(data: NSData) {
output(search(parse(data)))
}
}
struct SimpleSolver<Problem, Solution>: Solving {
let _parse: (NSData) -> Problem
let _search: (Problem) -> Solution
let _output: (Solution) -> Void
init(
parse: (NSData) -> Problem,
search: (Problem) -> Solution,
output: (Solution) -> Void) {
_parse = parse
_search = search
_output = output
}
func parse(data: NSData) -> Problem { return _parse(data) }
func search(problem: Problem) -> Solution { return _search(problem) }
func output(solution: Solution) { _output(solution) }
//...
}
protocol Solving {
typealias Problem
typealias Solution
var parse: (NSData) -> Problem { get }
var search: (Problem) -> Solution { get }
var output: (Solution) -> Void { get }
}
extension Solving {
func solve(data: NSData) {
output(search(parse(data)))
}
}
struct SimpleSolver<Problem, Solution>: Solving {
let parse: (NSData) -> Problem
let search: (Problem) -> Solution
let output: (Solution) -> Void
// ...
}
ENCAPSULATE WHAT
VARIES
SimpleSolver
Parser
Searcher
Outputter
“Simple” functions
FancySolver
Parser
Searcher
Outputter
“Fancy” functions
“Simple” properties “Fancy” properties
Solving
Parser
Searcher
Outputter
SimpleSolver
“Simple” functions
FancySolver
“Fancy” functions
“Simple” properties “Fancy” properties
SolverConfig
Parser
Searcher
Outputter
Config Config
struct SolverConfig<Problem, Solution> {
let parse: (NSData) -> Problem
let search: (Problem) -> Solution
let output: (Solution) -> Void
func solve(data: NSData) {
output(search(parse(data)))
}
}
struct SimpleSolver<Problem, Solution> {
let config: SolverConfig<Problem, Solution>
// ...
}
PARAMETERIZE ON DATA
TYPES, NOT HANDLER TYPES
protocol Parsing {
typealias Problem
func parse(data: NSData) -> Problem
}
protocol Searching {
typealias Problem
typealias Solution
func search(problem: Problem) -> Solution
}
protocol Outputting {
typealias Solution
func output(solution: Solution)
}
protocol Solving {
typealias Parser: Parsing
typealias Searcher: Searching
typealias Outputter: Outputting
var parser: Parser { get }
var searcher: Searcher { get }
var outputter: Outputter { get }
}
extension Solving where
Parser.Problem == Searcher.Problem,
Searcher.Solution == Outputter.Solution
{
func solve(data: NSData) {
outputter.output(searcher.search(parser.parse(data)))
}
}
struct SimpleSolver<
Parser: Parsing,
Searcher: Searching,
Outputter: Outputting
where
Parser.Problem == Searcher.Problem,
Searcher.Solution == Outputter.Solution
>: Solving {
let parser: Parser
let searcher: Searcher
let outputter: Outputter
// ...
}
struct SolverConfig<Problem, Solution> {
let parse: (NSData) -> Problem
let search: (Problem) -> Solution
let output: (Solution) -> Void
func solve(data: NSData) {
output(search(parse(data)))
}
}
struct SimpleSolver<Problem, Solution> {
let config: SolverConfig<Problem, Solution>
// ...
}
Protocols ⬌ Structs ⬌ Functions
Encapsulate What Varies
Parameterize on Data, not Handlers
REAL-WORLD PROTOCOLS
BEYOND CRUSTY
Rob Napier
robnapier.net
@cocoaphony
robnapier@gmail.com

dotSwift 2016 : Beyond Crusty - Real-World Protocols

  • 1.
  • 2.
    ANIMAL DOG CAT TIGER HOUSECATWOLFDOMESTIC CHIHUAHUA GREAT DANE PERSIAN MANX BIRD
  • 3.
    protocol Animal { funceat(food: Food) }
  • 4.
    protocol Animal { typealiasFood func eat(food: Food) }
  • 5.
    // An Animalcan eat protocol Animal { typealias Food func eat(food: Food) } struct Cow: Animal { func eat(food: Grass) { print("moo") } } struct Sheep: Animal { func eat(food: Grass) { print("bah") } } let grassEaters: [Animal] = [Cow(), Sheep()] error: protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
  • 6.
  • 7.
  • 8.
    let eat =Cow().eat
  • 9.
    let eat =Cow().eat eat(Grass()) let eat = { Cow().eat($0) } eat(Grass())
  • 10.
    struct AnyAnimal<Food>: Animal{ init<A: Animal where A.Food == Food>(_ animal: A) { _eat = animal.eat } func eat(food: Food) { _eat(food) } private let _eat: (Food) -> Void } let grassEaters = [AnyAnimal(Cow()), AnyAnimal(Sheep())] for eater in grassEaters { eater.eat(Grass()) }
  • 11.
    protocol Animal { typealiasFood func eat(food: Food) } struct AnyAnimal<Food>: Animal { func eat(food: Food) { _eat(food) } init ... private let _eat: (Food) -> Void }
  • 12.
    struct Animal<Food> { leteat: (Food) -> Void } protocol Animal { typealias Food func eat(food: Food) } let grassEaters = [ Animal(eat: cow.eat), // method Animal(eat: { _ in print("bah") } ) // function literal ]
  • 13.
    let session =NSURLSession() session.delegate = self let task = session.dataTaskWithURL(url) let task = session.dataTaskWithURL(url, completionHandler: { ... }) DELEGATES HANDLERS
  • 14.
    SOME RULES OFTHUMB ‣ One method? Maybe function. ‣ Three or more methods? Protocol. ‣ Relationship short-lived? Function. ‣ Relationship ongoing? Probably protocol/delegate.
  • 15.
    protocol P { typealiasA typealias B func x(a: A) -> B func y(a: A) -> B } struct S<A,B> { func x(a: A) -> B {…} func y(a: A) -> B {…} } func x<A,B>(a: A) -> B {…} func y<A,B>(a: A) -> B {…} Pick the form that works 
 best for your problem.
  • 16.
  • 17.
    protocol Parsing { typealiasProblem func parse(data: NSData) -> Problem } protocol Searching { typealias Problem typealias Solution func search(problem: Problem) -> Solution } protocol Outputting { typealias Solution func output(solution: Solution) }
  • 18.
    struct Solver< Parser: Parsing, Searcher:Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >{ let parser: Parser let searcher: Searcher let outputter: Outputter func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } }
  • 19.
    protocol Solving { typealiasParser: Parsing typealias Searcher: Searching typealias Outputter: Outputting var parser: Parser { get } var searcher: Searcher { get } var outputter: Outputter { get } } extension Solving where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution { func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } }
  • 20.
    struct SimpleSolver< Parser: Parsing, Searcher:Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >: Solving { let parser: Parser let searcher: Searcher let outputter: Outputter // ... }
  • 21.
    All the coderepetition was making him ornery, and if Crusty ain't happy, ain't nobody happy. Protocol-Oriented Programming in Swift
  • 22.
    protocol Solving { typealiasProblem typealias Solution func parse(data: NSData) -> Problem func search(problem: Problem) -> Solution func output(solution: Solution) -> Void } extension Solving { func solve(data: NSData) { output(search(parse(data))) } }
  • 23.
    struct SimpleSolver<Problem, Solution>:Solving { let _parse: (NSData) -> Problem let _search: (Problem) -> Solution let _output: (Solution) -> Void init( parse: (NSData) -> Problem, search: (Problem) -> Solution, output: (Solution) -> Void) { _parse = parse _search = search _output = output } func parse(data: NSData) -> Problem { return _parse(data) } func search(problem: Problem) -> Solution { return _search(problem) } func output(solution: Solution) { _output(solution) } //... }
  • 24.
    protocol Solving { typealiasProblem typealias Solution var parse: (NSData) -> Problem { get } var search: (Problem) -> Solution { get } var output: (Solution) -> Void { get } } extension Solving { func solve(data: NSData) { output(search(parse(data))) } } struct SimpleSolver<Problem, Solution>: Solving { let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void // ... }
  • 25.
  • 26.
  • 27.
    SimpleSolver “Simple” functions FancySolver “Fancy” functions “Simple”properties “Fancy” properties SolverConfig Parser Searcher Outputter Config Config
  • 28.
    struct SolverConfig<Problem, Solution>{ let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void func solve(data: NSData) { output(search(parse(data))) } } struct SimpleSolver<Problem, Solution> { let config: SolverConfig<Problem, Solution> // ... }
  • 29.
    PARAMETERIZE ON DATA TYPES,NOT HANDLER TYPES
  • 30.
    protocol Parsing { typealiasProblem func parse(data: NSData) -> Problem } protocol Searching { typealias Problem typealias Solution func search(problem: Problem) -> Solution } protocol Outputting { typealias Solution func output(solution: Solution) } protocol Solving { typealias Parser: Parsing typealias Searcher: Searching typealias Outputter: Outputting var parser: Parser { get } var searcher: Searcher { get } var outputter: Outputter { get } } extension Solving where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution { func solve(data: NSData) { outputter.output(searcher.search(parser.parse(data))) } } struct SimpleSolver< Parser: Parsing, Searcher: Searching, Outputter: Outputting where Parser.Problem == Searcher.Problem, Searcher.Solution == Outputter.Solution >: Solving { let parser: Parser let searcher: Searcher let outputter: Outputter // ... } struct SolverConfig<Problem, Solution> { let parse: (NSData) -> Problem let search: (Problem) -> Solution let output: (Solution) -> Void func solve(data: NSData) { output(search(parse(data))) } } struct SimpleSolver<Problem, Solution> { let config: SolverConfig<Problem, Solution> // ... }
  • 31.
    Protocols ⬌ Structs⬌ Functions Encapsulate What Varies Parameterize on Data, not Handlers
  • 32.
    REAL-WORLD PROTOCOLS BEYOND CRUSTY RobNapier robnapier.net @cocoaphony robnapier@gmail.com