Building Better Apps with Value Types in Swift Session 414

Similar documents
What s New in LLDB. Debug your way to fame and glory #WWDC15. Developer Tools. Session 402

Swift API Design Guidelines

Stanford CS193p. Developing Applications for ios. Winter CS193p! Winter 2015

What s New in Core Data?

Stanford CS193p. Developing Applications for ios. Spring CS193p. Spring 2016

Getting the Most out of Playgrounds in Xcode

Using and Extending the Xcode Source Editor

Leveraging Touch Input on ios

Thread Sanitizer and Static Analysis

Enhancing your apps for the next dimension of touch

Finding Bugs Using Xcode Runtime Tools

Optimizing Swift Performance Session 409

Introducing the Photos Frameworks

What s New in Foundation for Swift Session 207

What s New in MapKit. App Frameworks #WWDC17. Fredrik Olsson

Mastering Drag and Drop

Implementing UI Designs in Interface Builder

Introducing the Modern WebKit API

What s New in NSCollectionView Session 225

Creating Complications with ClockKit Session 209

WatchKit In-Depth, Part 2

Improving your Existing Apps with Swift

What's New in UIKit Dynamics and Visual Effects Session 229

Stanford CS193p. Developing Applications for ios. Winter CS193p. Winter 2017

Modern User Interaction on ios

Monday, 1 November The ios System

Accessibility on ios. Developing for everyone. Frameworks #WWDC14. Session 210 Clare Kasemset ios Accessibility

Seamless Linking to Your App

Advanced Debugging and the Address Sanitizer

Collections. Fall, Prof. Massimiliano "Max" Pala

What s New in Swift #WWDC18. Ted Kremenek, Languages & Runtimes Manager Slava Pestov, Swift Compiler Engineer

Mastering UIKit on tvos

Porting Objective-C to Swift. Richard Ekle

Profiling in Depth. Do you know where your code is? Session 412. Kris Markel Performance Tools Engineer Chad Woolf Performance Tools Engineer

Stanford CS193p. Developing Applications for ios. Winter CS193p! Winter 2015

What s New in Xcode App Signing

SWIFT! init(title: String) { self.title = title } // required initializer w/ named parameter

Understanding Undefined Behavior

Building Faster in Xcode

Introducing CloudKit. A how-to guide for icloud for your Apps. Frameworks #WWDC14. Session 208 Olivier Bonnet CloudKit Client Software

Stanford CS193p. Developing Applications for ios. Fall CS193p. Fall

Building Watch Apps #WWDC15. Featured. Session 108. Neil Desai watchos Engineer

LESSONS LEARNED. SWIFT. Dagna Bieda, 7th April 2016

What s New in tvos #WWDC16. App Frameworks. Session 206. Hans Kim tvos Engineer

Classes and Operators. Ali Malik

Cocoa Development Tips

Mobile Application Programming: ios. Value Types and Swift

Monetize and Promote Your App with iad

Getting Help. iphone Application Programming Lecture 3: Foundation Classes. Data Structures in Objective C. Online Documentation.

Stanford CS193p. Developing Applications for iphone 4, ipod Touch, & ipad Spring Stanford CS193p Spring 2011

Stanford CS193p. Developing Applications for ios. Fall CS193p. Fall

iphone Application Programming Lecture 3: Foundation Classes

Functions. Ali Malik

Introducing Swift Playgrounds

CS193p Spring 2010 Thursday, April 29, 2010

Data Delivery with Drag and Drop

Advanced Scrollviews and Touch Handling Techniques

COMP-520 GoLite Tutorial

Topic 7: Algebraic Data Types

What s New in imessage Apps

Topic 8: Lazy Evaluation

What s New in Notifications

For your convenience Apress has placed some of the front matter material after the index. Please use the Bookmarks and Contents at a Glance links to

Accessibility on OS X

CSE413: Programming Languages and Implementation Racket structs Implementing languages with interpreters Implementing closures

What s New in ARKit 2

Objective-C Primer. iphone Programmer s Association. Lorenzo Swank September 10, 2008

Assignment II: Foundation Calculator

Designing for Apple Watch

NSObject. - (NSString *)description Provides us with a string description of the object

Lecture 5 Demo Code: FaceIt MVC and Gestures

ITP 342 Mobile App Dev. Fundamentals

Stanford CS193p. Developing Applications for ios. Winter CS193p. Winter 2017

Adopting Advanced Features of the New UI

CloudKit Tips And Tricks

SWIFT BASICS

Introducing On Demand Resources

Advances in AVFoundation Playback

Kevin van Vechten Core OS

Mysteries of Auto Layout, Part 1

Advanced Memory Analysis with Instruments. Daniel Delwood Performance Tools Engineer

New UIKit Support for International User Interfaces

Creating Extensions for ios and OS X, Part Two

iteration & recursion

Software System Design and Implementation

Rust intro. (for Java Developers) JFokus #jfokus 1. 1

Quick Interaction Techniques for watchos

What s New in Cocoa for macos

Swift. Introducing swift. Thomas Woodfin

Extending Your Apps with SiriKit

CS 457/557: Functional Languages

Collections & Memory Management. Lecture 2

Introductory ios Development

iphone Application Programming Lab 3: Swift Types and Custom Operator + A02 discussion

ITP 342 Advanced Mobile App Dev. Core Data

ITP 342 Mobile App Development. Data Persistence

Working Effectively with Objective-C on iphone OS. Blaine Garst Wizard of Runtimes

CSE341: Programming Languages Lecture 9 Function-Closure Idioms. Dan Grossman Winter 2013

Overloading & Polymorphism

When Swift met classic algorithms and data structures. Caveats & Tips

Transcription:

Developer Tools #WWDC15 Building Better Apps with Value Types in Swift Session 414 Doug Gregor Language Lawyer Bill Dudney Arranger of Bits 2015 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.

Roadmap Reference semantics Immutability Value semantics Value types in practice Mixing value types and reference types

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

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()

Why Is It So Hot in Here?

Unintended Sharing let home = House() let temp = Temperature() temp.fahrenheit = 75 Thermostat House thermostat oven temperature Oven temp Temperature celsius: 23.88 temperature

Unintended Sharing let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp Thermostat House thermostat oven temperature Oven temp Temperature celsius: 23.88 temperature

Unintended Sharing let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 Thermostat House thermostat oven temperature Oven temp Temperature celsius: 218.3 temperature

Unintended Sharing let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake() Thermostat House thermostat oven temperature Oven temp Temperature celsius: 218.3 temperature

Copy When You Need It

Manual Copying let home = House() let temp = Temperature() temp.fahrenheit = 75 Thermostat House thermostat oven temperature Oven temp Temperature celsius: 23.88 temperature

Manual Copying let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp.copy() Thermostat House thermostat oven temperature Oven temp Temperature celsius: 23.88 temperature

Manual Copying let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp.copy() Temperature Thermostat celsius: 23.88 House thermostat oven temperature Oven temp Temperature celsius: 23.88 temperature

Manual Copying let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp.copy() temp.fahrenheit = 425 Temperature Thermostat celsius: 23.88 House thermostat oven temperature Oven temp Temperature celsius: 218.3 temperature

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() Thermostat temperature House thermostat oven Oven temperature temp Temperature celsius: 23.88 Temperature celsius: 218.3

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() Thermostat temperature House thermostat oven Oven temperature temp Temperature celsius: 23.88 Temperature celsius: 218.3 Temperature celsius: 218.3

Defensive Copying class Oven { var _temperature: Temperature = Temperature(celsius: 0) var temperature: Temperature { get { return _temperature set { _temperature = newvalue.copy()

Defensive Copying class Thermostat { var _temperature: Temperature = Temperature(celsius: 0) var temperature: Temperature { get { return _temperature set { _temperature = newvalue.copy()

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 in Cocoa 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 keys Property copy attribute provides defensive copying on assignment

Defensive Copying in Cocoa 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 keys Property copy attribute provides defensive copying on assignment It s still not enough bugs abound due to missed copies

Is Immutability the Answer?

Eliminating Mutation Functional programming languages have reference semantics with immutability Eliminates many problems caused by reference semantics with mutation No worries about unintended side effects

Eliminating 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 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

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)

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)

Sieve of Eratosthenes func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 9 0 11 0 13 0 15 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 9 0 11 0 13 0 15 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 9 0 11 0 13 0 15 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 0 0 11 0 13 0 0 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 0 0 11 0 13 0 0 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 0 0 11 0 13 0 0 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 0 0 11 0 13 0 0 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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

Sieve of Eratosthenes 2 3 0 5 0 7 0 0 0 11 0 13 0 0 0 17 0 19 0 func primes(n: Int) -> [Int] { var numbers = [Int](2..<n) 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 2 3 5 7 11 13 17 19

Functional Sieve of Eratosthenes Haskell: primes = sieve [2..] sieve [] = [] sieve (p : xs) = p : sieve [x x < xs, x mod p > 0]

Functional Sieve of Eratosthenes Haskell: 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 )

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19 5 7 11 13 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19 5 7 11 13 17 19

Functional Sieve of Eratosthenes 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 ) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 3 5 7 9 11 13 15 17 19 5 7 11 13 17 19

Functional Sieve Is Not the Real Sieve Performance differences matter Haskell: 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 ) O'Neill, Melissa E. The Genuine Sieve of Eratosthenes. Journal of Functional Programming, Vol. 19, No. 1. (2009), pp. 95-106

Functional Sieve Is Not the Real Sieve Performance differences matter Haskell: 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 ) O'Neill, Melissa E. The Genuine Sieve of Eratosthenes. Journal of Functional Programming, Vol. 19, No. 1. (2009), pp. 95-106

Immutability in Cocoa[Touch] Cocoa[Touch] has a number of immutable classes NSDate, NSURL, UIImage, NSNumber, etc. Improved safety (no need to use copy)

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];

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 NSArray<NSString *> *array = [NSArray arraywithobject: NSHomeDirectory()]; NSString *component; while ((component = getnextsubdir()) { array = [array arraybyaddingobject: component]; url = [NSURL fileurlwithpathcomponents: array];

Thoughtful Mutability in Cocoa[Touch] You d miss it if it were gone Cocoa[Touch] has a number of immutable classes NSDate, NSURL, UIImage, NSNumber, etc. Improved safety (no need to use copy) Thoughtful mutability NSMutableArray<NSString *> *array = [NSMutableArray array]; [array addobject: NSHomeDirectory()]; NSString *component; while ((component = getnextsubdir()) { [array addobject: component]; url = [NSURL fileurlwithpathcomponents: array];

Value Semantics

Variables Are Logically Distinct Integers are value types 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

Variables Are Logically Distinct CGPoints are value types Mutating one variable of some value type will never affect a different variable var a: CGPoint = CGPoint(x: 3, y: 5) var b = a assert(a == b) b.x = 17 print("a = \(a), b = \(b)") // a = (x = 3, y = 5), b = (x = 17, y = 5)

Variables Are Logically Distinct Strings are value types Mutating one variable of some value type will never affect a different variable var a: String = "Hello" var b = a assert(a == b) b.extend(" WWDC!") print("a = \(a), b = \(b)") // a = Hello, b = Hello WWDC!

Variables Are Logically Distinct Arrays are value types Mutating one variable of some value type will never affect a different variable var a: [Int] = [1, 2, 3, 4, 5] var b = a assert(a == b) b[2] = 17 print("a = \(a), b = \(b)") // a = [1, 2, 3, 4, 5], b = [1, 2, 17, 4, 5]

Variables Are Logically Distinct Dictionaries are value types Mutating one variable of some value type will never affect a different variable var a: [Int : String] = [1 : "uno", 2 : "dos"] var b = a assert(a == b) b[2] = "due" print("a = \(a), b = \(b)") // a = [1 : "uno", 2 : "dos"], // b = [1 : "uno", 2 : "due"]

Value Types Compose All of Swift s fundamental types are value types Int, Double, String,

Value Types Compose All of Swift s fundamental types are value types Int, Double, String, All of Swift s collections are value types Array, Set, Dictionary,

Value Types Compose All of Swift s fundamental types are value types Int, Double, String, All of Swift s collections are value types Array, Set, Dictionary, Swift tuples, structs, and enums that contain value types are value types

Value Types Are Distinguished by Value Equality is established by value of a variable Not its identity Not how we arrived at the value var a: Int = 5 var b: Int = 2 + 3 assert(a == b)

Value Types Are Distinguished by Value Equality is established by value of a variable Not its identity Not how we arrived at the value var a: CGPoint = CGPoint(x: 3, y: 5) var b: CGPoint = CGPoint(x: 1, y: 3) b.x += 2 b.y += 2 assert(a == b)

Value Types Are Distinguished by Value Equality is established by value of a variable Not its identity Not how we arrived at the value var a: String = "Hello WWDC!" var b: String = "Hello" b += " " b += "WWDC!" assert(a == b)

Value Types Are Distinguished by Value Equality is established by value of a variable Not its identity Not how we arrived at the value var a: [Int] = [1, 2, 3] var b: [Int] = [3, 2, 1].sort(<) assert(a == b)

Equatable Value types should implement 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

Equatable Value types should implement 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 var a = var b = a assert(a == b) assert(b == a) var c = b assert(c == a)

Implementing 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 extension CGPoint: Equatable { func ==(lhs: CGPoint, rhs: CGPoint) -> Bool { return lhs.x == rhs.x && lhs.y == rhs.y

Value Semantics Temperature struct Temperature: Equatable { var celsius: Double = 0 var fahrenheit: Double { get { return celsius * 9 / 5 + 32 set { celsius = (newvalue - 32) * 5 / 9 func ==(lhs: Temperature, rhs: Temperature) -> Bool { return lhs.celsius == rhs.celsius

Using Value Semantics Temperature let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake()

Using Value Semantics Temperature let home = House() let temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp error: cannot assign to property: temp is a let constant temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake()

Using Value Semantics Temperature let home = House() var temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake()

Using Value Semantics Temperature let home = House() var temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake() House thermostat oven Thermostat Temperature celsius: 23.88 Oven Temperature celsius: 218.3

Using Value Semantics Temperature Everywhere let home = House() var temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake() House Thermostat Temperature celsius: 23.88 Oven Temperature celsius: 218.3

Using Value Semantics Temperature Everywhere var home = House() var temp = Temperature() temp.fahrenheit = 75 home.thermostat.temperature = temp temp.fahrenheit = 425 home.oven.temperature = temp home.oven.bake() House Thermostat Temperature celsius: 23.88 Oven Temperature celsius: 218.3

Mutation When You Want It But not when you don t let means the value will never change let numbers = [1, 2, 3, 4, 5] var means you can update the value without affecting any other values var strings = [String]() for x in numbers { strings.append(string(x))

Freedom from Race Conditions var numbers = [1, 2, 3, 4, 5] scheduler.processnumbersasynchronously(numbers) for i in 0..<numbers.count { numbers[i] = numbers[i] * i scheduler.processnumbersasynchronously(numbers)

Performance What about all those copies? var numbers = [1, 2, 3, 4, 5] scheduler.processnumbersasynchronously(numbers) for i in 0..<numbers.count { numbers[i] = numbers[i] * i scheduler.processnumbersasynchronously(numbers)

Copies Are Cheap

Copies Are Cheap Constant time

Copies Are Cheap Constant time Copying a low-level, fundamental type is constant time Int, Double, etc.

Copies Are Cheap Constant time Copying a low-level, fundamental type is constant time Int, Double, etc. Copying a struct, enum, or tuple of value types is constant time CGPoint, etc.

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

Value Semantics Are Simple and Efficient Different variables are logically distinct Mutability when you want it Copies are cheap

Value Types in Practice Conceptualize an Example

A Diagram Made of Value Types Circle Polygon Diagram

Circle struct Circle: Equatable { var center: CGPoint var radius: Double CGFloat Circle Point CGFloat CGFloat init(center: CGPoint, radius: Double) { self.center = center self.radius = radius func ==(lhs: Circle, rhs: Circle) { return lhs.center == rhs.center && lhs.radius == rhs.radius

Polygon struct Polygon: Equatable { Polygon var corners: [CGPoint] = [] CGFloat Point CGFloat func ==(lhs: Polygon, rhs: Polygon) { return lhs.corners == rhs.corners Point CGFloat CGFloat Point CGFloat CGFloat

Diagram Contains Circles Polygon Circle 0..n Diagram

Diagram Contains Polygons Polygon 0..n Diagram Circle

Diagram Contains Polygons? 0..n Diagram Circle Polygon

Diagram Contains Polygons Protocols can abstract over value types Drawable 0..n Diagram Circle Polygon

Diagram Contains Polygons Protocols can abstract over value types Drawable 0..n Diagram Circle Polygon Protocol-Oriented Programming in Swift (Repeat) Pacific Heights Friday 3:30PM

The Drawable Protocol protocol Drawable { func draw()

The Drawable Protocol protocol Drawable { func draw() extension Polygon: Drawable { func draw() { let ctx = UIGraphicsGetCurrentContext() CGContextMoveToPoint(ctx, corners.last!.x corners.last!.y) for point in corners { CGContextAddLineToPoint(ctx, point.x, point.y) CGContextClosePath(ctx) CGContextStrokePath(ctx)

The Drawable Protocol protocol Drawable { func draw() extension Circle: Drawable { func draw() { let arc = CGPathCreateMutable() CGPathAddArc(arc, nil, center.x, center.y, radius, 0, 2 * π, true) CGContextAddPath(ctx, arc) CGContextStrokePath(ctx)

Creating the Diagram struct Diagram { var items: [Drawable] = []

Creating the Diagram struct Diagram { var items: [Drawable] = [] mutating func additem(item: Drawable) { items.append(item)

Creating the Diagram struct Diagram { var items: [Drawable] = [] mutating func additem(item: Drawable) { items.append(item) func draw() { for item in items { item.draw()

Adding Items var doc = Diagram() doc Diagram

Adding Items var doc = Diagram() doc.additem(polygon()) doc Diagram Polygon

Adding Items var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc Diagram Polygon Circle

Copied on Assignment var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) var doc2 = doc doc Diagram Polygon Circle doc2 Diagram Polygon Circle

Copied on Assignment Heterogeneous arrays have value semantics, too! var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) var doc2 = doc doc2.items[1] = Polygon(corners: points) doc Diagram Polygon Circle doc2 Diagram Polygon Polygon

Making Diagram Equatable extension Diagram: Equatable { func ==(lhs: Diagram, rhs: Diagram) { return lhs.items == rhs.items

Making Diagram Equatable extension Diagram: Equatable { func ==(lhs: Diagram, rhs: Diagram) { return lhs.items == rhs.items error: binary operator == cannot be applied to two [Drawable] operands

Making Diagram Equatable extension Diagram: Equatable { func ==(lhs: Diagram, rhs: Diagram) { return lhs.items == rhs.items error: binary operator == cannot be applied to two [Drawable] operands Protocol-Oriented Programming in Swift (Repeat) Pacific Heights Friday 3:30PM

If It Quacks Like a Duck protocol Drawable { func draw() struct Diagram { var items: [Drawable] = [] func draw() {

If It Quacks Like a Duck protocol Drawable { func draw() struct Diagram: Drawable { var items: [Drawable] = [] func draw() {

Diagram as a Drawable var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc Diagram Polygon Circle

Diagram as a Drawable var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc.additem(diagram()) doc Diagram Polygon Circle Diagram

Diagram as a Drawable var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc.additem(doc) doc Diagram Polygon Circle

Diagram as a Drawable var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc.additem(doc) doc Diagram Polygon Circle func draw() { for item in items { item.draw() magicmove magicmove

Diagram as a Drawable var doc = Diagram() doc.additem(polygon()) doc.additem(circle()) doc.additem(doc) doc Diagram Polygon Circle func draw() { for item in items { item.draw() magicmove magicmove Diagram Polygon Circle

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 var enabled: Bool //

A Value Type Can Contain a Reference Copies of the value type will share the reference struct ButtonWrapper { var button: Button

A Value Type Can Contain a Reference Copies of the value type will share the reference struct ButtonWrapper { var button: Button Maintaining value semantics requires special considerations How do we cope with mutation of the referenced object? How does the reference identity affect equality?

Immutable References struct Image : Drawable { var topleft: CGPoint var image: UIImage Image CGPoint CGFloat CGFloat UIImage

Immutable References struct Image : Drawable { var topleft: CGPoint image Image CGPoint var image: UIImage CGFloat CGFloat UIImage var image = Image(topLeft: CGPoint(x: 0, y: 0), image: UIImage(imageNamed:"San Francisco")!)

Immutable References Are Okay! Mutation of the referenced object does not occur struct Image : Drawable { var topleft: CGPoint image Image CGPoint var image: UIImage CGFloat CGFloat image2 Image CGPoint UIImage CGFloat CGFloat var image = Image(topLeft: CGPoint(x: 0, y: 0), var image2 = image image: UIImage(imageNamed:"San Francisco )!)

Immutable References and Equatable struct Image : Drawable { var topleft: CGPoint image Image CGPoint var image: UIImage CGFloat CGFloat image2 Image CGPoint UIImage CGFloat CGFloat extension Image : Equatable { func ==(lhs: Image, rhs: Image) -> Bool { return lhs.topleft == rhs.topleft && lhs.image === rhs.image

Immutable References and Equatable Reference identity is not enough struct Image : Drawable { var topleft: CGPoint image Image CGPoint var image: UIImage CGFloat CGFloat UIImage image2 Image CGPoint CGFloat CGFloat UIImage extension Image : Equatable { func ==(lhs: Image, rhs: Image) -> Bool { return lhs.topleft == rhs.topleft && lhs.image === rhs.image

Immutable References and Equatable Use deep equality comparisons struct Image : Drawable { var topleft: CGPoint image Image CGPoint var image: UIImage CGFloat CGFloat UIImage image2 Image CGPoint CGFloat CGFloat UIImage extension Image : Equatable { func ==(lhs: Image, rhs: Image) -> Bool { return lhs.topleft == rhs.topleft && lhs.image.isequal(rhs.image)

References to Mutable Objects struct BezierPath: Drawable { var path = UIBezierPath() BezierPath UIBezierPath var isempty: Bool { return path.empty func addlinetopoint(point: CGPoint) { path.addlinetopoint(point)

References to Mutable Objects struct BezierPath: Drawable { var path = UIBezierPath() BezierPath UIBezierPath var isempty: Bool { return path.empty func addlinetopoint(point: CGPoint) { path.addlinetopoint(point)

References to Mutable Objects Unexpected mutation struct BezierPath: Drawable { var path = UIBezierPath() BezierPath var isempty: Bool { UIBezierPath return path.empty BezierPath func addlinetopoint(point: CGPoint) { path.addlinetopoint(point)

Copy-on-Write Unrestricted mutation of referenced objects breaks value semantics Separate non-mutating operations from mutating ones Non-mutating operations are always safe Mutating operations must first copy

Copy-on-Write in Action struct BezierPath: Drawable { private var _path = UIBezierPath() var pathforreading: UIBezierPath { return _path BezierPath UIBezierPath

Copy-on-Write in Action struct BezierPath: Drawable { private var _path = UIBezierPath() var pathforreading: UIBezierPath { return _path var pathforwriting: UIBezierPath { mutating get { _path = _path.copy() as! UIBezierPath return _path BezierPath UIBezierPath

Copy-on-Write in Action extension BezierPath { var isempty: Bool { return pathforreading.empty BezierPath UIBezierPath func addlinetopoint(point: CGPoint) { pathforwriting.addlinetopoint(point)

Copy-on-Write in Action extension BezierPath { var isempty: Bool { return pathforreading.empty BezierPath UIBezierPath func addlinetopoint(point: CGPoint) { pathforwriting.addlinetopoint(point) error: cannot read pathforwriting because self is not mutable

Copy-on-Write in Action extension BezierPath { var isempty: Bool { return pathforreading.empty BezierPath UIBezierPath mutating func addlinetopoint(point: CGPoint) { pathforwriting.addlinetopoint(point)

Bezier Path Copy-on-write path BezierPath UIBezierPath var path = BezierPath() var path2 = path path.addlinetopoint(cgpoint(x: 100, y: 125))

Bezier Path Copy-on-write path BezierPath UIBezierPath path2 BezierPath var path = BezierPath() var path2 = path var path2 = path var path2 = path path.addlinetopoint(cgpoint(x: 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125))

Bezier Path Copy-on-write path BezierPath UIBezierPath path2 BezierPath var path = BezierPath() var path2 = path var path2 = path if path.empty { print("path is empty") var path2 = path path.addlinetopoint(cgpoint(x: var path2 = 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125))

Bezier Path Copy-on-write path BezierPath UIBezierPath path2 BezierPath var path = BezierPath() var path2 = path var path2 = path if path.empty { print("path is empty") var path2 = path var path.addlinetopoint(cgpoint(x: path2 = path 10, y: 20)) 100, y: 125)) path.addlinetopoint(cgpoint(x: var path2 = 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125))

Bezier Path Copy-on-write path BezierPath UIBezierPath path2 BezierPath UIBezierPath var path = BezierPath() var path2 = path var path2 = path if path.empty { print("path is empty") var path2 = path var path.addlinetopoint(cgpoint(x: path2 = path 10, y: 20)) 100, y: 125)) path.addlinetopoint(cgpoint(x: var path2 = 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125)) path.addlinetopoint(cgpoint(x: 100, y: 125))

Forming a Path from a Polygon extension Polygon { var path: BezierPath { var result = BezierPath() result.movetopoint(corners.last!) for point in corners { result.addlinetopoint(point) return result

Forming a Path from a Polygon Copies every time through the loop! extension Polygon { var path: BezierPath { var result = BezierPath() result.movetopoint(corners.last!) for point in corners { result.addlinetopoint(point) return result

Forming a Path from a Polygon Use the mutable reference type (carefully) extension Polygon { var path: BezierPath { var result = UIBezierPath() result.movetopoint(corners.last!) for point in corners { result.addlinetopoint(point) return BezierPath(path: result)

Uniquely Referenced Swift Objects struct MyWrapper { var _object: SomeSwiftObject var objectforwriting: SomeSwiftObject { mutating get { _object = _object.copy() return _object

Uniquely Referenced Swift Objects struct MyWrapper { var _object: SomeSwiftObject var objectforwriting: SomeSwiftObject { mutating get { if!isuniquelyreferencednonobjc(&_object)) { _object = _object.copy() return _object

Uniquely Referenced Swift Objects struct MyWrapper { var _object: SomeSwiftObject var objectforwriting: SomeSwiftObject { mutating get { if!isuniquelyreferencednonobjc(&_object)) { _object = _object.copy() return _object The standard library value types uses this throughout

Mixing Value Types and Reference Types Maintaining value semantics requires special considerations Copy-on-write enables efficient value semantics when wrapping Swift reference types

Implementing Undo with Value Types

Undo var doc = Diagram() var undostack: [Diagram] = [] doc Diagram undostack

Undo var doc = Diagram() var undostack: [Diagram] = [] undostack.append(doc) doc Diagram undostack Diagram

Undo var doc = Diagram() var undostack: [Diagram] = [] undostack.append(doc) doc.additem(polygon()) doc undostack Diagram Polygon Diagram

Undo doc Diagram var doc = Diagram() var undostack: [Diagram] = [] undostack.append(doc) doc.additem(polygon()) undostack.append(doc) undostack Polygon Diagram Diagram Polygon Diagram Polygon Circle

Undo doc Diagram Polygon Circle var doc = Diagram() var undostack: [Diagram] = [] undostack.append(doc) doc.additem(polygon()) undostack.append(doc) doc.additem(circle()) undostack.append(doc) undostack Diagram Diagram Polygon Diagram Polygon Circle

History

History

Value Semantics Photoshop uses value semantics Every action results in a doc instance Efficient because of copy-on-write Parent, Sean. Value Semantics and Concept-based Polymorphism. C++ Now!, 2012

Value Semantics Photoshop uses value semantics Every action results in a doc instance Efficient because of copy-on-write Parent, Sean. Value Semantics and Concept-based Polymorphism. C++ Now!, 2012

Value Semantics Photoshop uses value semantics Every action results in a doc instance Efficient because of copy-on-write Parent, Sean. Value Semantics and Concept-based Polymorphism. C++ Now!, 2012

Value Semantics Photoshop uses value semantics Every action results in a doc instance Efficient because of copy-on-write Parent, Sean. Value Semantics and Concept-based Polymorphism. C++ Now!, 2012

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

Related Sessions Protocol-Oriented Programming in Swift Mission Wednesday 2:30PM Optimizing Swift Performance Presidio Thursday 9:00AM Protocol-Oriented Programming in Swift (Repeat) Pacific Heights Friday 3:30PM

More Information Swift Language Documentation http://developer.apple.com/swift Apple Developer Forums http://developer.apple.com/forums Stefan Lesser Swift Evangelist slesser@apple.com