Skip to content

Latest commit

ย 

History

History
382 lines (277 loc) ยท 8.95 KB

BuildingBetterAppsWithValueTypesInSwift.md

File metadata and controls

382 lines (277 loc) ยท 8.95 KB

@ WWDC 15

Reference Semantics

A Temperature Class

class Temperature {
  var celsius: Double = 0
  var fahrenheit: Double {
    get { return celsius * 9 / 5 + 32 }
    set { celsius = (newValue - 32) * 5 / 9 }
  }
}

Using Our Temperature Class

let home = House()
let temp = Temperature()
temp.fahrenheit = 75
home.thermostat.temperature = temp

temp.fahrenheit = 425
home.oven.temperature = temp
home.oven.bake()

Unintended Sharing

home์˜ temperature์™€ oven์˜ temperature๊ฐ€ ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋‹ค.

sharing์„ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ๋Š” copy๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

Manual Copying

let home = House()
let temp = Temperature()
temp.fahrenheit = 75
home.thermostat.temperature = temp.copy()

temp.fahrenheit = 425
home.oven.temperature = temp.copy()
home.oven.bake()

Defensive Copying

class Oven {
  var _temperature: Temperature = Temperature(celsius: 0)
  
  var temperature: Temperature {
    get { return _temperature }
    set { _temperature = newValue.copy() }
  }
}

Defensive Copying in Cocoa[Touch] and Objective-C

  • Cocoa[Touch] requires copying throughout
    • NSCopying codifies copying an object
    • NSString, NSArray, NSDictionary, NSURLRequest, etc. all require copying
  • Defensive copying pervades Cocoa[Touch] and Objective-C
    • NSDictionary calls -copy on its key
    • Property copy attribute provides defensive copying on assignment
    • ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— copy ํ•˜์ง€๋งŒ ์„ฑ๋Šฅ์ด ์กฐ๊ธˆ ๋–จ์–ด์ง€๊ฒŒ ๋œ๋‹ค.

Immutability

Eliminating Mutation

reference semantics์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ mutation์˜ ๋ฌธ์ œ๊ฐ€ ์•„๋‹๊นŒ? ํ•˜๋Š” ๊ด€์ 

  • Functional programming languages have reference semantics with immutability

  • Eliminates many problems caused by reference semantics with mutation

    • No worries about unintended side effects
  • Several notable disadvantages > Immutability์˜ ๋‹จ์ ๋„ ์žˆ๋‹ค

    • Can lead to awkward interfaces
    • Does not map efficiently to the machine model

An Immutable Temperature Class

class Temperature {
  let celsius: Double = 0
  var fahrenheit: Double { return celsius * 9 / 5 + 32 }
  
  init(celsius: Double) { self.celsius = celsius }
  init(fahrenheit: Double) { self.celsius = (fahrenheit - 32) * 5 / 9 }
}

Awkward Immutable Interfaces

// With mutability
home.oven.temperature.fahrenheit += 10.0

// Without mutability
let temp = home.oven.temperature
home.oven.temperature = Temperature(fahrenheit: temp.fahrenheit + 10.0)

Immutability ๋•Œ๋ฌธ์— ์ง๊ด€์ ์ด์ง€ ์•Š์€, ์–ด์ƒ‰ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๊ฐ€ ์ž‘์„ฑ๋  ์ˆ˜ ์žˆ๋‹ค. ์‚ฌ์‹ค '='๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋˜ ๋‹ค๋ฅธ mutability๋ผ๊ณ  ๋ณผ ์ˆ˜๋„ ์žˆ๋‹ค.

Sieve of Eratosthenes (์—๋ผํ† ์Šคํ…Œ๋„ค์Šค์˜ ์ฒด ์•Œ๊ณ ๋ฆฌ์ฆ˜)

func primes(n: Int) -> [Int] {
  var numbers = [Int](2..<n) // Create an array
  for i in 0..<n-2 {
    guard let prime = numbers[i] where prime > 0 else { continue }
    for multiple in stride(from: 2 * prime-2, to: n-2, by: prime) {
      numbers[multiple] = 0
    }
  }
  return numbers.filter { $0 > 0 } // Only prime numbers
}

very simple algorithm!

Functional Sieve of Eratosthenes

ํ•˜์Šค์ผˆ ๋ฒ„์ „! Immutable!

primes = sieve [2..]
sieve [] = []
sieve (p : xs) = p : sieve [x | x <- xs, x 'mod' p > 0]

Swift ๋ฒ„์ „!

func sieve(numbers: [Int]) -> [Int] {
  if numbers.isEmpty { return [] }
  let p = numbers[0]
  return [p] + sieve(numbers[1..<numbers.count].filter { $0 % p > 0 })
}

The Genuine Sieve of Eratosthenes

์ด non-mutating version์€ mutating version์— ๋น„ํ•ด ํšจ์œจ์ ์ด์ง€ ๋ชปํ•˜๋‹ค.

Immutability in Cocoa[Touch]

  • Cocoa[Touch] has a number of immutable classes

    • NSDate, NSURL, UIImage, NSNumber, etc.
    • Improved safety (no need to use copy)
  • Downsides to immutability

    NSURL *url = [[NSURL alloc] initWithString: NSHomeDirectory()];
    NSString *component;
    while (component == getNextSubdir()) {
      url = [url URLByAppendingPathComponent: component];
    }

    ๊ณ„์† object๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ด ๋ฐฉ์‹์€ ๋น„ํšจ์œจ์ ์ด๋‹ค.

    NSArray<NSString *> *array = [NSArray arrayWithObject: NSHomeDirectory()];
    NSString *component;
    while (component == getNextSubdir()) {
      array = [array arrayByAddingObject: component];
    }
    url = [NSURL fileURLWithPathComponents: array];
  • Thoughtful mutability

    NSMutableArray<NSString *> *array = [NSMutableArray array];
    NSString *component;
    while (component == getNextSubdir()) {
      array = [array addObject: component];
    }
    url = [NSURL fileURLWithPathComponents: array];

Value Semantics

mutability์˜ ์žฅ์ ๋„ ์žˆ๋‹ค. ๊ฑฑ์ •๋˜๋Š” ๊ฒƒ์€ sharing! ๊ทธ๋ž˜์„œ value semantics๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Variables Are Logically Distinct

  • Mutating one variable of some value type will never affect a different variable

    var a: Int = 17
    var b = a
    assert(a == b)
    b += 25
    print("a = \(a), b = \(b)") // a = 17, b = 42

Value Types Compose

  • ์Šค์œ„ํ”„ํŠธ์˜ ๋ชจ๋“  ๊ธฐ๋ณธ(fundamental) ํƒ€์ž…์€ value ํƒ€์ž…์ด๋‹ค.
    • Int, Double, String, ...
  • ์Šค์œ„ํ”„ํŠธ์˜ ๋ชจ๋“  collection์€ value ํƒ€์ž…์ด๋‹ค.
    • Array, Set, Dictionary, ...
  • ์Šค์œ„ํ”„ํŠธ์˜ tuple, struct, enum ๊ฐ™์€ value ํƒ€์ž…์„ ํฌํ•จํ•˜๋Š” ๊ฒƒ๋“ค๋„ value ํƒ€์ž…์ด๋‹ค.

Value Types Are Distingushed by Value

  • Value ํƒ€์ž…์€ ๊ฐ’์— ์˜ํ•ด ๊ตฌ๋ถ„๋œ๋‹ค.
    • Identity ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋‹ค.
    • ์–ด๋–ป๊ฒŒ ๊ฐ’์„ ๊ฐ€์ง€๋Š” ์ง€๋Š” ์ค‘์š”ํ•œ ํฌ์ธํŠธ๊ฐ€ ์•„๋‹ˆ๋‹ค.

Equatable

Value ํƒ€์ž…์€ Equatable์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.

protocol Equatable {
  /// Reflexive - `x == x` is `true`
  /// Symmetric - `x == y` then `y == x`
  /// Transitive - `x == y` and `y == z` then `x == z`
  func ==(lhs: Self, rhs: Self) -> Bool
}

Using Value Semantics Everywhere

var home = House()
var temp = Temperature()
temp.fahrenheit = 75
home.thermostat.temperature = temp

temp.fahrenheit = 425
home.oven.temperature = temp
home.oven.bake()

Mutation When You Want It

But not when you don't

let์€ ๊ฐ’์ด ์ ˆ๋Œ€ ๋ฐ”๋€Œ์ง€ ์•Š์Œ์„ ์˜๋ฏธ

let numbers = [1, 2, 3, 4, 5]

var์€ ๋‹ค๋ฅธ ๊ฐ’๋“ค์—๋Š” ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด์„œ ๊ฐ’์„ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธ

var strings = [String]()
for x in numbers {
  strings.append(String(x))
}

Freedom from Race Conditions

var numbers = [1, 2, 3, 4, 5]
schedule.processNumbersAsynchronously(numbers)
for i in 0..<numbers.count { numbers[i] = numbers[i] * i }
schedule.processNumbersAsynchronously(numbers)

Reference semantics๋ฅผ ๊ฐ€์กŒ์—ˆ๋”๋ผ๋ฉด numbers๋Š” Race condition์„ ๊ฒช๊ฒŒ ๋˜์—ˆ์„ ๊ฒƒ์ด๋‹ค.

Copies Are Cheap

Constant time

  • Copying a low-level, fundamental type is constant time
    • Int, Double, ...
  • Copying a struct, enum, or tuple of value types is constant time
    • CGPoint, ...
  • Extensible data structures use copy-on-write
    • Copying involves a fixed number of reference-counting operations
    • String, Array, Set, Dictionary, ...

Mixing Value Types and Reference Types

Reference Types Often Contain Value Types

  • Value types generally used for "primitive" data of objects

    class Button: Control {
      var label: String // primitive data
      var enabled: Bool // primitive data
    }

A Value Type Can Contain a Reference

  • Value ํƒ€์ž…์„ ๋ณต์‚ฌํ•˜๋ฉด reference๋ฅผ ๊ณต์œ ํ•œ๋‹ค.

    struct ButtonWrapper {
      var button: Button // ButtonWrapper๋ฅผ ๋ณต์‚ฌํ•œ ๊ฐ์ฒด๋Š” ์ด button์„ ๊ณต์œ ํ•œ๋‹ค.
    }
  • Value semantics๋ฅผ ์œ ์ง€ํ•˜๋ ค๋ฉด ํŠน๋ณ„ํ•œ ๊ณ ๋ ค๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

    • Referened object์˜ mutation์„ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•  ์ง€
    • Reference์˜ identity๊ฐ€ equality์— ์–ด๋–ค ์˜ํ–ฅ์„ ๋ฏธ์น  ์ง€

Copy-on-Write

  • Referenced object์— ๋Œ€ํ•œ ์ œํ•œ๋˜์ง€ ์•Š์€ mutation์€ value semantics๋ฅผ ๋ง๊ฐ€๋œจ๋ฆฐ๋‹ค
  • Mutating ์‹œํ‚ค๋Š” ์ž‘์—…์„ non-mutating ์ž‘์—…๋“ค๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค
    • Non-mutating ์ž‘์—…์€ ํ•ญ์ƒ ์•ˆ์ „ํ•˜๋‹ค.
    • Mutating operations must first copy
struct BezierPath: Drawable {
  private var _path = UIBezierPath()
  
  var pathForReading: UIBezierPath {
    return _path
  }
  
  var pathForWriting: UIBezierPath {
    mutating get {
      _path = _path.copy() as! UIBezierPath
      return _path
    }
  }
}

extension BezierPath {
  var isEmpty: Bool {
    return pathForReading.empty
  }
  
 	mutating func addLineToPoint(point: CGPoint) {
    pathForWriting.addLineToPoint(point)
  }
}

Uniquely Referenced Swift Objects

struct MyWrapper {
  var _object: SomeSwiftObject
  var objectForWriting: SomeSwiftObject {
    mutating get {
      if !isUniquelyReferencedNonObjC(&_object) {
        _object = _object.copy()
      }
      return _object
    }
  }
}

Summary

  • Reference semantics and unexpected mutation
  • Value semantics solve these problems
  • Expressiveness of mutability, safety of immutability