419 Protocol and Value Oriented Programming in Uikit Apps
419 Protocol and Value Oriented Programming in Uikit Apps
© 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
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
dream2
// Reference Semantics
class Dream {
var description: String
var creature: Creature
var effects: Set<Effect>
...
} dream1
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)
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)
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)
}
.unicorn(.yellow)
// Value Semantics
struct Dream {
var description: String
var creature: Creature
var effects: Set<Effect> dream1
Saw the light
... .unicorn(.yellow)
}
struct Dream {
var description: String
var creature: Creature
var effects: Set<Effect> dream1
Saw the light
... .unicorn(.yellow)
}
struct Dream {
var description: String
😃
var creature: Creature
var effects: Set<Effect> dream1
Saw the light
... .unicorn(.yellow)
}
The Internet™
“Use values only for simple model types.”
The Internet™
View
Cell layout
UIView
UIKit
UITableViewCell
DecoratingLayoutCell
Cell Layout
UIView
UIKit
UITableViewCell
DecoratingLayoutCell
Cell Layout
UIView
UIKit
UITableViewCell
DecoratingLayoutCell
Cell Layout
UIView
UIKit
UITableViewCell
DecoratingLayoutCell
UIView
UIKit
UITableViewCell
UITableViewCell
Layout
Cell Layout
UITableViewCell
UIView Layout
Cell Layout
UITableViewCell
// Perform layout...
}
// View Layout
struct DecoratingLayout {
var content: UIView
var decoration: UIView
// Perform layout...
}
// View Layout
struct DecoratingLayout {
var content: UIView
var decoration: UIView
func testLayout() {
let child1 = UIView()
let child2 = UIView()
// Testing
func testLayout() {
let child1 = UIView()
let child2 = UIView()
func testLayout() {
let child1 = UIView()
let child2 = UIView()
func testLayout() {
let child1 = UIView()
let child2 = UIView()
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 }
}
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 }
}
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 }
}
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 }
}
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 }
}
protocol Layout {
var frame: CGRect { get set }
}
protocol Layout {
var frame: CGRect { get set }
}
protocol Layout {
var frame: CGRect { get set }
}
return dstRect
}
// Inheritance
required init?(coder aDecoder: NSCoder) {
fatalError("\(#function) has not been implemented")
}
}
imageView.contentMode = .scaleAspectFit
return imageView
}
content.text = dream.description
for subview in contentView.subviews {
subview.removeFromSuperview()
}
addSubviews()
setNeedsLayout()
}
}
// MARK: Initialization
addSubviews()
}
// MARK: Layout
/*
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
decoration.contentMode = .scaleAspectFit
contentView.addSubview(content)
contentView.addSubview(decoration)
setNeedsLayout()
content.frame = contentLayoutFrame
decoration.frame = decorationLayoutFrame
}
return dstRect
}
return dstRect
}
accessories = (0..<dream.numberOfCreatures).map { _ in
let imageView = UIImageView(image: dream.creature.image)
imageView.contentMode = .scaleAspectFit
return imageView
}
content.text = dream.description
// MARK: Initialization
addSubviews()
}
// MARK: Layout
/*
This is the intersection between the UIKit view code and this sample's
}
}
decoration.contentMode = .scaleAspectFit
contentView.addSubview(content)
contentView.addSubview(decoration)
setNeedsLayout()
super.layoutSubviews()
content.frame = contentLayoutFrame
decoration.frame = decorationLayoutFrame
}
dstRect.origin.x = rect.size.width / 3
dstRect.size.width *= 2/3
return dstRect
return dstRect
// MARK: Properties
accessories = (0..<dream.numberOfCreatures).map { _ in
imageView.contentMode = .scaleAspectFit
return imageView
content.text = dream.description
subview.removeFromSuperview()
}
addSubviews()
setNeedsLayout()
}
}
// MARK: Initialization
addSubviews()
}
// MARK: Layout
}
}
super.layoutSubviews()
/*
This is the intersection between the UIKit view code and this sample's
}
}
// MARK: Properties
decoration.image = creature.image
}
}
didSet {
content.text = title
// MARK: Initialization
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)
// MARK: Layout
super.layoutSubviews()
/*
This is the intersection between the UIKit view code and this sample's
}
// Inheritance
decoration.contentMode = .scaleAspectFit
contentView.addSubview(content)
contentView.addSubview(decoration)
setNeedsLayout()
super.layoutSubviews()
content.frame = contentLayoutFrame
decoration.frame = decorationLayoutFrame
}
dstRect.origin.x = rect.size.width / 3
dstRect.size.width *= 2/3
return dstRect
return dstRect
// MARK: Properties
accessories = (0..<dream.numberOfCreatures).map { _ in
imageView.contentMode = .scaleAspectFit
return imageView
content.text = dream.description
subview.removeFromSuperview()
}
addSubviews()
setNeedsLayout()
}
}
// MARK: Initialization
addSubviews()
}
// MARK: Layout
}
}
super.layoutSubviews()
/*
This is the intersection between the UIKit view code and this sample's
}
}
// MARK: Properties
decoration.image = creature.image
}
}
didSet {
content.text = title
// MARK: Initialization
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)
// MARK: Layout
super.layoutSubviews()
/*
This is the intersection between the UIKit view code and this sample's
}
Composition
Share code without reducing local reasoning
Composition of Views
Composition of Views
Composition of Views
Composition of Views
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)
}
protocol Layout {
mutating func layout(in rect: CGRect)
}
protocol Layout {
mutating func layout(in rect: CGRect)
}
protocol Layout {
mutating func layout(in rect: CGRect)
protocol Layout {
mutating func layout(in rect: CGRect)
protocol Layout {
mutating func layout(in rect: CGRect)
associatedtype Content
var contents: [Content] { get }
}
// Associated Type
NodeDecoratingLayout
SKNode SKNode
// Associated Type
DecoratingLayout<SKNode>
SKNode SKNode
// Associated Type
DecoratingLayout<SKNode>
SKNode SKNode
// Associated Type
UIView UIView
mutating func layout(in rect: CGRect)
typealias Content = Child.Content
var contents: [Content] { get }
}
// Associated Type
CascadingLayout UIView
mutating func layout(in rect: CGRect)
typealias Content = Child.Content
var contents: [Content] { get }
UIView
}
// Associated Type
protocol Layout {
mutating func layout(in rect: CGRect)
associatedtype Content
var contents: [Content] { get }
}
// Testing
func testLayout() {
let child1 = UIView()
let child2 = UIView()
func testLayout() {
let child1 = TestLayout()
let child2 = TestLayout()
func testLayout() {
let child1 = TestLayout()
let child2 = TestLayout()
z
Alex Migicovsky Swift Compiler Typo Engineer
?!@##?
// DreamListViewController — Undo Bug
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
Model
}
// DreamListViewController — Isolating the Model
...
}
...
}
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
}
}
// DreamListViewController — Isolating the Model
}
}
// DreamListViewController — Isolating the Model
...
}
}
// DreamListViewController — Isolating the Model
...
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
...
}
...
}
// DreamListViewController — Invalid UI State Bug
...
}
// DreamListViewController — Isolating UI State
...
}
enum State {
}
// DreamListViewController — Isolating UI State
...
}
enum State {
}
// DreamListViewController — Isolating UI State
...
}
enum State {
caseisInViewingMode:
var viewing Bool
case
var sharingDreams:
sharing(dreams:
[Dream]?
[Dream])
case
var selectedRows:
selecting(selectedRows:
IndexSet? IndexSet)
}
// DreamListViewController — Isolating UI 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
CascadingLayout DecoratingLayout
Model View Controller
Controller
CascadingLayout DecoratingLayout
Model View Controller
Controller
InsetLayout
Model View Controller
Controller
InsetLayout
Techniques and Tools
Techniques and Tools
https://developer.apple.com/wwdc16/419
Related Sessions