0% found this document useful (0 votes)
27 views

419 Protocol and Value Oriented Programming in Uikit Apps

Swift wenben

Uploaded by

xh26nk4r2f
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views

419 Protocol and Value Oriented Programming in Uikit Apps

Swift wenben

Uploaded by

xh26nk4r2f
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 218

Developer Tools #WWDC16

Protocol and Value Oriented


Programming in UIKit Apps
Swift in practice
Session 419

Jacob Xiao Protocol Oriented Programmer


Alex Migicovsky Swift Compiler Typo Engineer

© 2016 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.
Local Reasoning
Model View Controller

Controller

Model View
Lucid Dreams
Think Different

Protocol-Oriented Programming in Swift WWDC 2015

Building Better Apps with Value Types in Swift WWDC 2015


Overview

Value types and protocols


• Recap—Model
• Focus—View and controller
• Testing

Sample code: https://developer.apple.com/go/?id=lucid-dreams


Model
What’s a dream?
// Reference Semantics

class Dream {
var description: String
var creature: Creature
var effects: Set<Effect>

...
}
// Reference Semantics

class Dream {
var description: String
var creature: Creature
var effects: Set<Effect>

...
} dream1

Saw the light


var dream1 = Dream(...)
var dream2 = dream1 .unicorn(.yellow)

dream2
// Reference Semantics

class Dream {
var description: String
var creature: Creature
var effects: Set<Effect>

...
} dream1

Saw the light


var dream1 = Dream(...)
var dream2 = dream1 .unicorn(.yellow)

dream2.description = "Unicorns all over"


dream2
// Reference Semantics

class Dream {
var description: String
var creature: Creature
var effects: Set<Effect>

...
} dream1

Unicorns
Saw theall
light
over
var dream1 = Dream(...)
var dream2 = dream1 .unicorn(.yellow)

dream2.description = "Unicorns all over"


dream2
// Reference Semantics

class Dream {
var description: String
var creature: Creature
var effects: Set<Effect> ?!@##?
...
} dream1

Unicorns
Saw theall
light
over
var dream1 = Dream(...)
var dream2 = dream1 .unicorn(.yellow)

dream2.description = "Unicorns all over"


dream2
Relationships

App Delegate

Navigation VC

Favorite
Dreams VC Dream VC
Creature VC

Dream Creature
Relationships

App Delegate

Navigation VC

Favorite
Dreams VC Dream VC
Creature VC

Dream Creature
Relationships—It's Complicated…

App Delegate

Navigation VC

Favorite
Dreams VC Dream VC
Creature VC

Dream Creature
Relationships—It's Complicated…

App Delegate

Navigation VC

Favorite
Dreams VC Dream VC
Creature VC

Dream Creature
Relationships—It's Complicated…

App Delegate

Navigation VC

Favorite
Dreams VC Dream VC
Creature VC

Dream Creature
// Value Semantics

struct Dream {
var description: String
var creature: Creature
var effects: Set<Effect> dream1
Saw the light

... .unicorn(.yellow)
}

var dream1 = Dream(...)


dream2
var dream2 = dream1
Saw the light

.unicorn(.yellow)
// Value Semantics

struct Dream {
var description: String
var creature: Creature
var effects: Set<Effect> dream1
Saw the light

... .unicorn(.yellow)
}

var dream1 = Dream(...)


dream2
var dream2 = dream1
Unicorns
Saw theall
light
over
dream2.description = "Unicorns all over"
.unicorn(.yellow)
// Value Semantics

struct Dream {
var description: String
var creature: Creature
var effects: Set<Effect> dream1
Saw the light

... .unicorn(.yellow)
}

var dream1 = Dream(...)


dream2
var dream2 = dream1
Unicorns
Saw theall
light
over
dream2.description = "Unicorns all over"
.unicorn(.yellow)
// Value Semantics

struct Dream {
var description: String
😃
var creature: Creature
var effects: Set<Effect> dream1
Saw the light

... .unicorn(.yellow)
}

var dream1 = Dream(...)


dream2
var dream2 = dream1
Unicorns
Saw theall
light
over
dream2.description = "Unicorns all over"
.unicorn(.yellow)
“Use values only for simple model types.”

The Internet™
“Use values only for simple model types.”

The Internet™
View
Cell layout

Jacob Xiao Protocol Oriented Programmer


Cell Layout

UIView

UIKit
UITableViewCell

DecoratingLayoutCell
Cell Layout

UIView

UIKit
UITableViewCell

DecoratingLayoutCell
Cell Layout

UIView

UIKit
UITableViewCell

DecoratingLayoutCell
Cell Layout

UIView

UIKit
UITableViewCell

DecoratingLayoutCell

DreamCell Saw the light


Cell Layout

UIView

UIKit
UITableViewCell

Saw the light DreamDetailView DecoratingLayoutCell

DreamCell Saw the light


Cell Layout

UITableViewCell

Layout
Cell Layout

UITableViewCell

UIView Layout
Cell Layout

UITableViewCell

UIView Layout SKNode


// Cell Layout

class DecoratingLayoutCell : UITableViewCell {


var content: UIView
var decoration: UIView

// Perform layout...
}
// View Layout

struct DecoratingLayout {
var content: UIView
var decoration: UIView

// Perform layout...
}
// View Layout

struct DecoratingLayout {
var content: UIView
var decoration: UIView

mutating func layout(in rect: CGRect) {


// Perform layout...
}
}
// View Layout
Saw the light

class DreamCell : UITableViewCell {


...

override func layoutSubviews() {


var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
decoratingLayout.layout(in: bounds)
}
}
// View Layout
Saw the light

class DreamCell : UITableViewCell {


...

override func layoutSubviews() {


var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
decoratingLayout.layout(in: bounds)
}
}

class DreamDetailView : UIView {


...

override func layoutSubviews() {


var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
decoratingLayout.layout(in: bounds)
}
}
// View Layout
Saw the light

class DreamCell : UITableViewCell {


...

override func layoutSubviews() {


var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
decoratingLayout.layout(in: bounds)
}
}

class DreamDetailView : UIView {


...

override func layoutSubviews() {


var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
decoratingLayout.layout(in: bounds)
}
}
// Testing
// Testing

func testLayout() {
let child1 = UIView()
let child2 = UIView()
// Testing

func testLayout() {
let child1 = UIView()
let child2 = UIView()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))
// Testing

func testLayout() {
let child1 = UIView()
let child2 = UIView()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))
// Testing

func testLayout() {
let child1 = UIView()
let child2 = UIView()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

XCTAssertEqual(child1.frame, CGRect(x: 0, y: 5, width: 35, height: 30))


XCTAssertEqual(child2.frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}
Local Reasoning
Easier to understand, easier to test
// View Layout

struct DecoratingLayout {
var content: UIView
var decoration: UIView
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// View Layout

struct DecoratingLayout {
var content: UIView
var decoration: UIView
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// SpriteKit Layout

struct ViewDecoratingLayout {
var content: UIView
var decoration: UIView
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

struct NodeDecoratingLayout {
var content: SKNode
var decoration: SKNode
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Layout

struct NodeDecoratingLayout {
var content: SKNode
var decoration: SKNode
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Layout

struct DecoratingLayout {
var content:
var decoration:
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Layout

struct DecoratingLayout {
var content:
var decoration:
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Layout

struct DecoratingLayout {
var content: Layout
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}
// Layout

struct DecoratingLayout {
var content: Layout
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}
// Layout

struct DecoratingLayout {
var content: Layout
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout {
var content: Layout
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout {
var content: Layout
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout {
var content: Layout UIView
var decoration: Layout
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout {
var content: Layout UIView
var decoration: Layout SKNode
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout<Child : Layout> {


var content: Child
var decoration: Child
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout<Child : Layout> {


var content: Child
var decoration: Child
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
// Layout

struct DecoratingLayout<Child : Layout> {


var content: Child
Must be the same
var decoration: Child
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}

protocol Layout {
var frame: CGRect { get set }
}

extension UIView : Layout {}


extension SKNode : Layout {}
Generic Types

More control over types


Can be optimized more at compile time
Generic Types

More control over types


Can be optimized more at compile time

Understanding Swift Performance Mission Friday 11:00AM


Sharing Code
Sharing Code
Sharing Code
var dstRect = contentView.bounds
dstRect.size.width /= 3

return dstRect
}

// Inheritance
required init?(coder aDecoder: NSCoder) {
fatalError("\(#function) has not been implemented")
}
}

class DreamCell: DecoratingLayoutCell {


// MARK: Properties

static let reuseIdentifier = "\(DreamCell.self)"

var content = UILabel()


var accessories = [UIImageView]()

var dream: Dream! {


didSet {
// Update the UI when the `dream` changes.
accessories = (0..<dream.numberOfCreatures).map { _ in
let imageView = UIImageView(image: dream.creature.image)

imageView.contentMode = .scaleAspectFit
return imageView
}

content.text = dream.description
for subview in contentView.subviews {

subview.removeFromSuperview()
}
addSubviews()
setNeedsLayout()
}
}

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubviews()
}

required init?(coder aDecoder: NSCoder) {


fatalError("\(#function) has not been implemented")
}

// MARK: Layout

private func addSubviews() {


let multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)

for view in multiPaneLayout.contents {


contentView.addSubview(view)
}
}

override func layoutSubviews() {


super.layoutSubviews()

/*
This is the intersection between the UIKit view code and this sample's
value based layout system.

*/
var multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)
multiPaneLayout.layout(in: contentView.bounds)
}

}
// Inheritance

class DecoratingLayoutCell : UITableViewCell {


var content: UIView!
var decoration: UIView!

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {


super.init(style: style, reuseIdentifier: reuseIdentifier)

decoration.contentMode = .scaleAspectFit

contentView.addSubview(content)
contentView.addSubview(decoration)
setNeedsLayout()

override func layoutSubviews() {


super.layoutSubviews()

content.frame = contentLayoutFrame
decoration.frame = decorationLayoutFrame
}

var contentLayoutFrame: CGRect {


var dstRect = contentView.bounds
dstRect.origin.x = rect.size.width / 3
dstRect.size.width *= 2/3

return dstRect
}

var decorationLayoutFrame: CGRect {


var dstRect = contentView.bounds
dstRect.size.width /= 3

return dstRect
}

required init?(coder aDecoder: NSCoder) {

fatalError("\(#function) has not been implemented")


}

class DreamCell: DecoratingLayoutCell {


// MARK: Properties

static let reuseIdentifier = "\(DreamCell.self)"

var content = UILabel()


var accessories = [UIImageView]()

var dream: Dream! {


didSet {
// Update the UI when the `dream` changes.

accessories = (0..<dream.numberOfCreatures).map { _ in
let imageView = UIImageView(image: dream.creature.image)

imageView.contentMode = .scaleAspectFit
return imageView
}
content.text = dream.description

for subview in contentView.subviews {


subview.removeFromSuperview()
}
addSubviews()
setNeedsLayout()
}

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {


super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubviews()
}

required init?(coder aDecoder: NSCoder) {


fatalError("\(#function) has not been implemented")

// MARK: Layout

private func addSubviews() {

let multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)


for view in multiPaneLayout.contents {
contentView.addSubview(view)
}
}

override func layoutSubviews() {


super.layoutSubviews()

/*
This is the intersection between the UIKit view code and this sample's

value based layout system.


*/
var multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)
multiPaneLayout.layout(in: contentView.bounds)

}
}

class CreatureCell: DreamCell {


// MARK: Properties
// Inheritance

class DecoratingLayoutCell : UITableViewCell {

var content: UIView!

var decoration: UIView!

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

decoration.contentMode = .scaleAspectFit

contentView.addSubview(content)

contentView.addSubview(decoration)

setNeedsLayout()

override func layoutSubviews() {

super.layoutSubviews()

content.frame = contentLayoutFrame

decoration.frame = decorationLayoutFrame
}

var contentLayoutFrame: CGRect {

var dstRect = contentView.bounds

dstRect.origin.x = rect.size.width / 3
dstRect.size.width *= 2/3

return dstRect

var decorationLayoutFrame: CGRect {


var dstRect = contentView.bounds
dstRect.size.width /= 3

return dstRect

required init?(coder aDecoder: NSCoder) {

fatalError("\(#function) has not been implemented")

class DreamCell: DecoratingLayoutCell {

// MARK: Properties

static let reuseIdentifier = "\(DreamCell.self)"

var content = UILabel()


var accessories = [UIImageView]()

var dream: Dream! {


didSet {
// Update the UI when the `dream` changes.

accessories = (0..<dream.numberOfCreatures).map { _ in

let imageView = UIImageView(image: dream.creature.image)

imageView.contentMode = .scaleAspectFit
return imageView

content.text = dream.description

for subview in contentView.subviews {

subview.removeFromSuperview()
}

addSubviews()

setNeedsLayout()

}
}

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubviews()
}

required init?(coder aDecoder: NSCoder) {


fatalError("\(#function) has not been implemented")
}

// MARK: Layout

private func addSubviews() {

let multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)

for view in multiPaneLayout.contents {


contentView.addSubview(view)

}
}

override func layoutSubviews() {

super.layoutSubviews()

/*

This is the intersection between the UIKit view code and this sample's

value based layout system.


*/

var multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)


multiPaneLayout.layout(in: contentView.bounds)

}
}

class CreatureCell: DreamCell {

// MARK: Properties

static let reuseIdentifier = "\(CreatureCell.self)"

private var content = UILabel()

private var decoration = UIImageView()

var creature: Dream.Creature! {


didSet {

decoration.image = creature.image

}
}

var title = "" {

didSet {

content.text = title

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

decoration.contentMode = .scaleAspectFit

// Add our subviews based on the ordering of the decorating layout's contents.
let decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
for view in decoratingLayout.contents {

contentView.addSubview(view)

required init?(coder aDecoder: NSCoder) {

fatalError("\(#function) has not been implemented")

// MARK: Layout

override func layoutSubviews() {

super.layoutSubviews()

/*
This is the intersection between the UIKit view code and this sample's

value based layout system.


*/

var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)


decoratingLayout.layout(in: contentView.bounds)
}

}
// Inheritance

class DecoratingLayoutCell : UITableViewCell {

var content: UIView!

var decoration: UIView!

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

decoration.contentMode = .scaleAspectFit

contentView.addSubview(content)

contentView.addSubview(decoration)

setNeedsLayout()

override func layoutSubviews() {

super.layoutSubviews()

content.frame = contentLayoutFrame

decoration.frame = decorationLayoutFrame
}

var contentLayoutFrame: CGRect {

var dstRect = contentView.bounds

dstRect.origin.x = rect.size.width / 3
dstRect.size.width *= 2/3

return dstRect

var decorationLayoutFrame: CGRect {


var dstRect = contentView.bounds
dstRect.size.width /= 3

return dstRect

required init?(coder aDecoder: NSCoder) {

fatalError("\(#function) has not been implemented")

class DreamCell: DecoratingLayoutCell {

// MARK: Properties

static let reuseIdentifier = "\(DreamCell.self)"

var content = UILabel()


var accessories = [UIImageView]()

var dream: Dream! {


didSet {
// Update the UI when the `dream` changes.

accessories = (0..<dream.numberOfCreatures).map { _ in

let imageView = UIImageView(image: dream.creature.image)

imageView.contentMode = .scaleAspectFit
return imageView

content.text = dream.description

for subview in contentView.subviews {

subview.removeFromSuperview()
}

addSubviews()

setNeedsLayout()

}
}

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubviews()
}

required init?(coder aDecoder: NSCoder) {


fatalError("\(#function) has not been implemented")
}

// MARK: Layout

private func addSubviews() {

let multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)

for view in multiPaneLayout.contents {


contentView.addSubview(view)

}
}

override func layoutSubviews() {

super.layoutSubviews()

/*

This is the intersection between the UIKit view code and this sample's

value based layout system.


*/

var multiPaneLayout = MultiPaneLayout(content: content, accessories: accessories)


multiPaneLayout.layout(in: contentView.bounds)

}
}

class CreatureCell: DreamCell {

// MARK: Properties

static let reuseIdentifier = "\(CreatureCell.self)"

private var content = UILabel()

private var decoration = UIImageView()

var creature: Dream.Creature! {


didSet {

decoration.image = creature.image

}
}

var title = "" {

didSet {

content.text = title

// MARK: Initialization

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

super.init(style: style, reuseIdentifier: reuseIdentifier)

decoration.contentMode = .scaleAspectFit

// Add our subviews based on the ordering of the decorating layout's contents.
let decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
for view in decoratingLayout.contents {

contentView.addSubview(view)

required init?(coder aDecoder: NSCoder) {

fatalError("\(#function) has not been implemented")

// MARK: Layout

override func layoutSubviews() {

super.layoutSubviews()

/*
This is the intersection between the UIKit view code and this sample's

value based layout system.


*/

var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)


decoratingLayout.layout(in: contentView.bounds)
}

}
Composition
Share code without reducing local reasoning
Composition of Views
Composition of Views
Composition of Views
Composition of Views

Classes instances are expensive!


Composition of Views

Classes instances are expensive!


Composition of Views Values!

Classes instances are expensive!


Composition of Views Values!

Classes instances are expensive!


Structs are cheap
Composition of Views Values!

Classes instances are expensive!


Structs are cheap
Composition is better with value semantics
// Composition of Values

struct CascadingLayout<Child : Layout> {


var children: [Child]
mutating func layout(in rect: CGRect) {
...
}
}
// Composition of Values

struct CascadingLayout<Child : Layout> {


var children: [Child]
mutating func layout(in rect: CGRect) {
...
}
}

struct DecoratingLayout<Child : Layout> {


var content: Child
var decoration: Child
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Composition of Values

struct CascadingLayout<Child : Layout> {


var children: [Child]
mutating func layout(in rect: CGRect) {
...
}
}

struct DecoratingLayout<Child : Layout> {


var content: Child
var decoration: Child
mutating func layout(in rect: CGRect) {
content.frame = ...
decoration.frame = ...
}
}
// Composition of Values

protocol Layout {
var frame: CGRect { get set }
}
// Composition of Values

protocol Layout {
mutating func layout(in rect: CGRect)
}
// Composition of Values

protocol Layout {
mutating func layout(in rect: CGRect)
}

extension UIView : Layout { ... }


extension SKNode : Layout { ... }
// Composition of Values

protocol Layout {
mutating func layout(in rect: CGRect)
}

extension UIView : Layout { ... }


extension SKNode : Layout { ... }

struct DecoratingLayout<Child : Layout> : Layout { ... }


struct CascadingLayout<Child : Layout> : Layout { ... }
// Composition of Values

protocol Layout {
mutating func layout(in rect: CGRect)
}

extension UIView : Layout { ... }


extension SKNode : Layout { ... }

struct DecoratingLayout<Child : Layout, ...> : Layout { ... }


struct CascadingLayout<Child : Layout> : Layout { ... }
// Composition of Values

let decoration = CascadingLayout(children: accessories)


var composedLayout = DecoratingLayout(content: content, decoration: decoration)
composedLayout.layout(in: rect)
// Composition of Values

let decoration = CascadingLayout(children: accessories)


var composedLayout = DecoratingLayout(content: content, decoration: decoration)
composedLayout.layout(in: rect)
// Composition of Values

let decoration = CascadingLayout(children: accessories)


var composedLayout = DecoratingLayout(content: content, decoration: decoration)
composedLayout.layout(in: rect)
// Composition of Values

let decoration = CascadingLayout(children: accessories)


var composedLayout = DecoratingLayout(content: content, decoration: decoration)
composedLayout.layout(in: rect)
Contents
// Contents

protocol Layout {
mutating func layout(in rect: CGRect)

var contents: [Layout] { get }


}
// Contents

protocol Layout {
mutating func layout(in rect: CGRect)

var contents: [Layout] { get } UIView and SKNode


}
// Associated Type

protocol Layout {
mutating func layout(in rect: CGRect)
associatedtype Content
var contents: [Content] { get }
}
// Associated Type

struct ViewDecoratingLayout : Layout {


...
ViewDecoratingLayout

mutating func layout(in rect: CGRect)


typealias Content = UIView
UIView UIView
var contents: [Content] { get }
}
// Associated Type

struct ViewDecoratingLayout : Layout {


...
ViewDecoratingLayout

mutating func layout(in rect: CGRect)


typealias Content = UIView
UIView UIView
var contents: [Content] { get }
}

struct NodeDecoratingLayout : Layout {


...
NodeDecoratingLayout

mutating func layout(in rect: CGRect)


typealias Content = SKNode
SKNode SKNode
var contents: [Content] { get }
}
// Associated Type

struct NodeDecoratingLayout : Layout {


...
ViewDecoratingLayout

mutating func layout(in rect: CGRect)


typealias Content = SKNode
UIView UIView
var contents: [Content] { get }
}

NodeDecoratingLayout

SKNode SKNode
// Associated Type

struct DecoratingLayout<Child : Layout> : Layout {


...
DecoratingLayout<UIView>

mutating func layout(in rect: CGRect)


typealias Content =
UIView UIView
var contents: [Content] { get }
}

DecoratingLayout<SKNode>

SKNode SKNode
// Associated Type

struct DecoratingLayout<Child : Layout> : Layout {


...
DecoratingLayout<UIView>

mutating func layout(in rect: CGRect)


typealias Content = Child.Content
UIView UIView
var contents: [Content] { get }
}

DecoratingLayout<SKNode>

SKNode SKNode
// Associated Type

struct DecoratingLayout<Child : Layout> : Layout {

var content: Child DecoratingLayout

var decoration: Child

UIView UIView
mutating func layout(in rect: CGRect)
typealias Content = Child.Content
var contents: [Content] { get }
}
// Associated Type

struct DecoratingLayout<Child : Layout> : Layout {

var content: Child DecoratingLayout

var decoration: Child

CascadingLayout UIView
mutating func layout(in rect: CGRect)
typealias Content = Child.Content
var contents: [Content] { get }
UIView
}
// Associated Type

struct DecoratingLayout<Child : Layout, Decoration : Layout


where Child.Content == Decoration.Content> : Layout {
var content: Child
var decoration: Decoration

mutating func layout(in rect: CGRect)


typealias Content = Child.Content
var contents: [Content] { get }
}
// Associated Type

struct DecoratingLayout<Child : Layout, Decoration : Layout


where Child.Content == Decoration.Content> : Layout {
var content: Child
var decoration: Decoration

mutating func layout(in rect: CGRect)


typealias Content = Child.Content
var contents: [Content] { get }
}
// Associated Type

struct DecoratingLayout<Child : Layout, Decoration : Layout


where Child.Content == Decoration.Content> : Layout {
var content: Child
var decoration: Decoration

mutating func layout(in rect: CGRect)


typealias Content = Child.Content
var contents: [Content] { get }
}
// Layout

protocol Layout {
mutating func layout(in rect: CGRect)

associatedtype Content
var contents: [Content] { get }
}
// Testing

func testLayout() {
let child1 = UIView()
let child2 = UIView()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

XCTAssertEqual(layout.contents[0].frame, CGRect(x: 0, y: 5, width: 35, height: 30))


XCTAssertEqual(layout.contents[1].frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}
// Testing

func testLayout() {
let child1 = TestLayout()
let child2 = TestLayout()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

XCTAssertEqual(layout.contents[0].frame, CGRect(x: 0, y: 5, width: 35, height: 30))


XCTAssertEqual(layout.contents[1].frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}

struct TestLayout : Layout {


var frame: CGRect
...
}
// Testing

func testLayout() {
let child1 = TestLayout()
let child2 = TestLayout()

var layout = DecoratingLayout(content: child1, decoration: child2)


layout.layout(in: CGRect(x: 0, y: 0, width: 120, height: 40))

XCTAssertEqual(layout.contents[0].frame, CGRect(x: 0, y: 5, width: 35, height: 30))


XCTAssertEqual(layout.contents[1].frame, CGRect(x: 35, y: 5, width: 70, height: 30))
}

struct TestLayout : Layout {


var frame: CGRect
...
}
Techniques
Techniques

Local reasoning with value types


Techniques

Local reasoning with value types


Generic types for fast, safe polymorphism
Techniques

Local reasoning with value types


Generic types for fast, safe polymorphism
Composition of values
Controller
Undo

Alex Migicovsky Swift Compiler Typo Engineer


Controller
Undo

z
Alex Migicovsky Swift Compiler Typo Engineer
?!@##?
// DreamListViewController — Undo Bug

class DreamListViewController : UITableViewController {


var dreams: [Dream]
var favoriteCreature: Creature
Model properties
...
}
Undo Registration

dreams
Undo Registration

dreams favoriteCreature
Undo Registration

dreams favoriteCreature
Undo Registration

dreams favoriteCreature
Undo Registration

dreams favoriteCreature
Undo Registration

dreams favoriteCreature
Undo Registration

Model

dreams favoriteCreature
Undo Registration

Model

dreams favoriteCreature favoriteMoment


Undo Registration

Model

dreams favoriteCreature favoriteMoment


// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


var dreams: [Dream]
var favoriteCreature: Creature
...
}

struct Model : Equatable {

}
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {

...
}

struct Model : Equatable {


var dreams: [Dream]
var favoriteCreature: Creature
}
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


var model: Model

...
}

struct Model : Equatable {


var dreams: [Dream]
var favoriteCreature: Creature
}
Model UndoManager Stack

dreams.removeLast()
.unicorn(.yellow) tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.pink)
dreams[0] tableView.reloadRows(...)

dreams[1] dreams.append(Dream(...))
tableView.insertRows(at: ...)
Model UndoManager Stack

dreams.removeLast()
.unicorn(.yellow) tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.pink)
dreams[0] tableView.reloadRows(...)

dreams[1] dreams.append(Dream(...))
tableView.insertRows(at: ...)
Model UndoManager Stack

dreams.removeLast()
.unicorn(.yellow) tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.pink)
dreams[0] tableView.reloadRows(...)

dreams.append(Dream(...))
tableView.insertRows(at: ...)
Model UndoManager Stack

favoriteCreature = .unicorn(.pink)
.unicorn(.yellow) tableView.reloadRows(...)

dreams.append(Dream(...))
dreams[0] tableView.insertRows(at: ...)
Model UndoManager Stack

favoriteCreature = .unicorn(.pink)
.unicorn(.yellow) tableView.reloadRows(...)

dreams.append(Dream(...))
dreams[0] tableView.insertRows(at: ...)
Model UndoManager Stack

favoriteCreature = .unicorn(.pink)
.unicorn(.pink) tableView.reloadRows(...)

dreams.append(Dream(...))
dreams[0] tableView.insertRows(at: ...)
Model UndoManager Stack

dreams.append(Dream(...))
.unicorn(.pink) tableView.insertRows(at: ...)

dreams[0]
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid
update: invalid number of rows in section 1. The number
of rows contained in an existing section after the update
(2) must be equal to the number of rows contained in that
section before the update (2), plus or minus the number
of rows inserted or deleted from that section (1
inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0
moved out).'
Model UndoManager Stack

dreams.removeLast()
.unicorn(.yellow) tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.pink)
dreams[0] tableView.reloadRows(...)

dreams[1] dreams.append(Dream(...))
tableView.insertRows(at: ...)
dreams.removeLast()
tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.pink)
tableView.reloadRows(...)

dreams.append(Dream(...))
tableView.insertRows(at: ...)
dreams.removeLast()
tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.yellow)
tableView.reloadRows(...)

favoriteCreature = .unicorn(.white)
tableView.reloadRows(...)

favoriteCreature = .unicorn(.pink)
tableView.reloadRows(...)

dreams.insert(Dream(...), at: 5)
tableView.insertRows(at: ...)

dreams.insert(Dream(...), at: 5)
tableView.insertRows(at: ...)

dreams.append(Dream(...))
tableView.insertRows(at: ...)
dreams.removeLast()
tableView.deleteRows(at: ...)

favoriteCreature = .unicorn(.yellow)
tableView.reloadRows(...)

favoriteCreature = .unicorn(.white)
tableView.reloadRows(...)

favoriteCreature = .unicorn(.pink)
tableView.reloadRows(...)

dreams.insert(Dream(...), at: 5)
tableView.insertRows(at: ...)

dreams.insert(Dream(...), at: 5)
tableView.insertRows(at: ...)

dreams.append(Dream(...))
tableView.insertRows(at: ...)
Model UndoManager Stack

.unicorn(.yellow) .unicorn(.yellow)

dreams[0] dreams[0]

dreams[1]

.unicorn(.pink)
Model UndoManager Stack

.unicorn(.yellow) .unicorn(.yellow)

dreams[0] dreams[0]

dreams[1]

.unicorn(.pink)
Model UndoManager Stack

.unicorn(.yellow)

dreams[0]

dreams[1]

.unicorn(.pink)
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


...
func modelDidChange(old: Model, new: Model) {

}
}
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


...
func modelDidChange(old: Model, new: Model) {
if old.favoriteCreature != new.favoriteCreature {
// Reload table view section for favorite creature.
tableView.reloadSections(...)
}

}
}
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


...
func modelDidChange(old: Model, new: Model) {
if old.favoriteCreature != new.favoriteCreature {
// Reload table view section for favorite creature.
tableView.reloadSections(...)
}

...

}
}
// DreamListViewController — Isolating the Model

class DreamListViewController : UITableViewController {


...
func modelDidChange(old: Model, new: Model) {
if old.favoriteCreature != new.favoriteCreature {
// Reload table view section for favorite creature.
tableView.reloadSections(...)
}

...

undoManager?.registerUndo(withTarget: self, handler: { target in


target.model = old
})
}
}
Benefits

Single code path


Benefits

Single code path


• Better local reasoning
Benefits

Single code path


• Better local reasoning
Values compose well with other values
Controller
UI state
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
viewing

selecting

sharing
var isInViewingMode: Bool

var selectedRows: IndexSet?

var sharingDreams: [Dream]?


// DreamListViewController — Invalid UI State Bug

class DreamListViewController : UITableViewController {

var isInViewingMode: Bool

...
}

var selectedRows: IndexSet?

var sharingDreams: [Dream]?


// DreamListViewController — Invalid UI State Bug

class DreamListViewController : UITableViewController {


var isInViewingMode: Bool
var sharingDreams: [Dream]?
var selectedRows: IndexSet?

...
}
// DreamListViewController — Invalid UI State Bug

class DreamListViewController : UITableViewController {


var isInViewingMode: Bool
var sharingDreams: [Dream]? UI State
var selectedRows: IndexSet?

...
}
// DreamListViewController — Isolating UI State

class DreamListViewController : UITableViewController {


caseisInViewingMode:
var viewing Bool
casesharingDreams:
var sharing(dreams: [Dream])
[Dream]?
caseselectedRows:
var selecting(selectedRows:
IndexSet? IndexSet)

...
}

enum State {

}
// DreamListViewController — Isolating UI State

class DreamListViewController : UITableViewController {


caseisInViewingMode:
var viewing Bool
casesharingDreams:
var sharing(dreams: [Dream])
[Dream]?
caseselectedRows:
var selecting(selectedRows:
IndexSet? IndexSet)

...
}

enum State {

}
// DreamListViewController — Isolating UI State

class DreamListViewController : UITableViewController {

...
}

enum State {
caseisInViewingMode:
var viewing Bool
case
var sharingDreams:
sharing(dreams:
[Dream]?
[Dream])
case
var selectedRows:
selecting(selectedRows:
IndexSet? IndexSet)
}
// DreamListViewController — Isolating UI State

class DreamListViewController : UITableViewController {


var state: State

...
}

enum State {
case viewing
case sharing(dreams: [Dream])
case selecting(selectedRows: IndexSet)
}
Recap
Model View Controller

Controller

Model View
Model View Controller

Controller

Dream View
Model View Controller

Controller

Dream View

CascadingLayout DecoratingLayout
Model View Controller

Controller

Dream Controller.Model View

CascadingLayout DecoratingLayout
Model View Controller

Controller

Dream Controller.Model Controller.State View

CascadingLayout DecoratingLayout
Model View Controller

Controller

Dream Controller.Model Controller.State View

Creature Effect CascadingLayout DecoratingLayout

InsetLayout
Model View Controller

Controller

Dream Controller.Model Controller.State View

Creature Effect CascadingLayout DecoratingLayout

InsetLayout
Techniques and Tools
Techniques and Tools

Customization through composition


Techniques and Tools

Customization through composition


Protocols for generic, reusable code
Techniques and Tools

Customization through composition


Protocols for generic, reusable code
Taking advantage of value semantics
Techniques and Tools

Customization through composition


Protocols for generic, reusable code
Taking advantage of value semantics
Local reasoning
More Information

https://developer.apple.com/wwdc16/419
Related Sessions

Understanding Swift Performance Mission Friday 11:00AM

Protocol-Oriented Programming in Swift WWDC 2015

Building Better Apps with Value Types in Swift WWDC 2015

You might also like