Skip to content

Latest commit

ย 

History

History
357 lines (218 loc) ยท 7.69 KB

Protocol and Value Oriented Programming in UIKit Apps.md

File metadata and controls

357 lines (218 loc) ยท 7.69 KB

@ WWDC 16

Local Reasoning

local reasoning์ด ๋ญ๋ƒ, ํ•œ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•  ๋•Œ ์™ธ๋ถ€ ์ฝ”๋“œ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ์ƒ๊ฐํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•จ

image

์ด ์•ฑ์˜ ํ˜„์žฌ ์ƒํƒœ. ๊ต‰์žฅํžˆ ๋ณต์žกํ•˜๋‹ค. ์ด๋ฅผ ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ์‹œ์ผœ๊ฐ€๋ฉด์„œ protocol๊ณผ value type์— ๋Œ€ํ•ด ๊ณต๋ถ€ํ•˜๋Š” ๊ฒƒ์ด ์ด๋ฒˆ ์„ธ์…˜์˜ ๋ชฉํ‘œ!

View

์ธํ„ฐ๋„ท์—์„œ๋Š” ํ”ํžˆ ๊ฐ„๋‹จํ•œ ๋ชจ๋ธ ํƒ€์ž…๋“ค์— value ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋ผ๊ณ  ๋งํ•˜์ง€๋งŒ ์ด๋Š” ํ‹€๋ ธ๋‹ค! UIKit์—์„œ value ํƒ€์ž…์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ์ง€ ์‚ดํŽด๋ณด์ž!

image

ํ˜„์žฌ cell์€ UITableViewCell ์ด๊ณ  ๊ทธ ์•ˆ์— UIView ๋“ค์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ณ , SKNode๊ฐ€ ์žˆ์„ ์ˆ˜๋„ ์žˆ๋‹ค! ์ด๊ฑธ value ํƒ€์ž…์ธ Layout์„ ์ด์šฉํ•ด์„œ ๊ตฌ์กฐ ๋ณ€๊ฒฝ์„ ํ•ด๋ณด์ž.

class DecoratingLayoutCell: UITableViewCell {
  var content: UIView
  var decoration: UIView
  
  // Perform layout...
}

์ด๊ฑธ struct๋กœ ๋ฐ”๊ฟ”๋ณด์ž.

struct DecoratingLayout {
  var content: UIView
  var decoration: UIView
  
  mutating func layout(in rect: CGRect) {
    // Perform layout...
  }
}

class DreamCell: UITableViewCell {
  ...
  override func layoutSubviews() {
    var decoratingLayout = DecoratingLayout(content: content, decoration: decoration)
    decoratingLayout.layout(in: bounds)
  }
}

์ด๋Ÿฐ ์‹์œผ๋กœ ์ชผ๋” ํ™•์žฅ์„ฑ์ด ์ƒ๊ฒผ๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋‹ค๋ฅธ cell์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹ค๋ฅธ ๋ทฐ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒ ์ง€?

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: 40))
}

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ํ…Œ์ŠคํŠธ๋„ ๊ฐ€๋Šฅํ•  ๊ฑฐ์•ผ!

Local Reasoning์œผ๋กœ ์šฐ๋ฆฌ๋Š” ์ข€ ๋” ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ณ , ์‰ฝ๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์ง€!

๊ทผ๋ฐ ์œ„์—์„œ ๋ดค๋˜ DecoratingLayout์€ content์™€ decoration์„ UIView ํƒ€์ž…์œผ๋กœ ๊ฐ–๊ณ  ์žˆ์–ด. ๊ทธ๋Ÿผ SKNode ๋ฅผ content ๋‚˜ decoration ์œผ๋กœ ๊ฐ–๊ณ  ์‹ถ๋‹ค๋ฉด ๋˜ ํ•˜๋‚˜์˜ Layout์„ ์ƒ์„ฑํ•ด์•ผ ๋ผ. NodeDecratingLayout ์ด๋Ÿฐ ์‹์œผ๋กœ.

ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€.

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์„ ์„ค์ •ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” frame ์ด ํ•„์š”ํ•ด. ๊ทธ๋ž˜์„œ frame ์„ ๊ฐ€์ง€๋„๋ก ํ•˜๋Š” protocol, Layout ์„ ๋งŒ๋“ค์—ˆ๊ณ , ์ด๊ฑธ content ์™€ decoration ์˜ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ–ˆ์–ด.

๊ทผ๋ฐ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ๊ณผ ์•ฝ๊ฐ„ ๋‹ฌ๋ผ! ์™œ๋ƒ๋ฉด content ์™€ decoration ์€ ๋‘˜ ๋‹ค UIView ์ด๊ฑฐ๋‚˜ SKNode ์ด๊ธฐ๋ฅผ ๋ฐ”๋ผ๋Š”๋ฐ, ์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” 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 {}

๊ทธ๋ž˜์„œ ์ด๋ ‡๊ฒŒ Generic์„ ์ด์šฉํ•ด์„œ content ์™€ decoration์ด ๊ฐ™์€ ํƒ€์ž…์ด๋„๋ก ๋งŒ๋“ค์–ด์คฌ์–ด.

Generic์„ ์ด์šฉํ•˜๋ฉด ํƒ€์ž…๋“ค์„ ๋” ์ž˜ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ๊ณ , ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ข€ ๋” ์ตœ์ ํ™”๋ฅผ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด.

์ž์„ธํ•œ ์ •๋ณด๋Š” Understanding Swift Performance๋ฅผ ์ฐธ๊ณ ํ•˜๋„๋ก~~

Composition์„ ์ด์šฉํ•ด์„œ ์šฐ๋ฆฌ๋Š” ์ฝ”๋“œ๋ฅผ local reasoning์„ ํ•ด์น˜์ง€ ์•Š๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๋Š” ํž™ ์˜์—ญ์— ์ €์žฅ๋˜๊ณ  ๋น„์šฉ์ด ๋น„์‹ธ. ์šฐ๋ฆฌ๋Š” ๊ทธ๋ž˜์„œ view์˜ composition์ด ์•„๋‹Œ value์˜ composition์„ ํ• ๊ฑฐ์•ผ. struct๋Š” ์ €๋ ดํ•˜๊ฑฐ๋“ ! composition์€ value semantics์™€ ํ•จ๊ป˜ํ•  ๋•Œ ๋” ์ข‹์•„.

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 = ...
  }
}

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

composition์„ ์–ด๋–ป๊ฒŒ ํ•ด ์ค˜์•ผ ํ• ๊นŒ? ๋จผ์ € Layout ํ”„๋กœํ† ์ฝœ์„ ์ข€ ๋ฐ”๊ฟ€๊ฑฐ์•ผ.

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

์ด๋ ‡๊ฒŒ!

extension UIView: Layout { ... }
extension SKNode: Layout { ... }

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

๋บŒ~!!

let decoration = CascadingLayout(children: accessories)
var composedLayout = DecoratingLayout(content: content, decoration: decoration)
composedLayout.layout(in: rect)

์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•œ๋‹ค!

protocol Layout {
  mutating func layout(in rect: CGRect)
  
  var contents: [Layout] { get }
}

๊ทผ๋ฐ contents๊ฐ€ UIView ์ด๊ฑฐ๋‚˜ SKNode๊ฑฐ๋‚˜ ๋‘˜ ์ค‘ ํ•˜๋‚˜์˜€์Œ ์ข‹๊ฒ ์–ด. ๊ทธ๋ž˜์„œ associatedtype ์„ ์ด์šฉํ• ๊ฑฐ์•ผ.

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

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์€ ํ…Œ์ŠคํŠธํ•˜๊ธฐ๋„ ์‰ฌ์›Œ

Techniques

์—ฌ๊ธฐ์„œ ์ •๋ฆฌ ํ•œ ๋ฒˆ ํ• ๊ฒŒ์šค

  • Local reasoning with value types
  • Generic types for fast, safe polymorphism
  • Composition of values

Controller - undo

undo๋ฅผ ํ•˜๋ ค๊ณ  ํ•  ๋•Œ

image

์ด๋ ‡๊ฒŒ ํ•˜์ง€ ๋ง๊ณ 

image

์ด๋ ‡๊ฒŒ ํ•˜๋ ค๊ณ  ํ•ด๋ด!

class DreamListViewController: UITableViewController {
  var dreams: [Dream]
  var favoriteCreature: Creature
  
	...
} // ์ด๋žฌ๋˜ ๊ฑธ ์•„๋ž˜์ฒ˜๋Ÿผ ๋ชจ๋ธ๋กœ ๋ถ„๋ฆฌ์‹œ์ผœ๋ณด์Ÿˆ

struct Model: Equatable {
  var dreams: [Dream]
  var favoriteCreature: Creature
}

์ญ‰์ญ‰ ๊ณ„์†ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค

class DreamListViewController: UITableViewController {
  ...
  func modelDidChange(old: Model, new: Model) {
    if old.favoriteCreature != new.favoriteCreature {
      tableView.reloadSections(...)
    }
    
    ...
    
    undoManager?.registerUndo(withTarget: self, handler: { target in
      target.model = old
    })
  }
}

Benefits

์š”๋žฌ์„ ๋•Œ ์ด์ ์€

  • Single code path
    • Better lcal reasoning
  • Values compose well with other values

Controller - UI state

class DreamListViewController: UITableViewController {
  var isInViewingMode: Bool
  var sharingDreams: [Dream]?
  var selectedRows: IndexSet?
  
  ...
}

์ด๋Ÿฐ ๊ฒƒ๋“ค์ด UI State๋‹ค! ์ด๋Ÿฐ State๋Š” enum์œผ๋กœ ๊ด€๋ฆฌํ•ด๋ณด์ž.

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

class DreamListViewController: UITableViewController {
  var state: State
  
  ...
}

๊ทธ๋Ÿผ value type์ด๊ธฐ์— ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ์ด์ ๋“ค์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด

image

Techniques and Tools

  • Customization through composition
  • Protocols for generics, reusable code
  • Taking advantage of value semantics
  • Local reasoning <- ์ด๊ฑด ์–ด๋–ค ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ์ค‘์š”ํ•˜๋‹ˆ๊นŒ ๊ผญ ์—ผ๋‘์— ๋‘๊ณ  ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜์ž๊ตฌ