Slide 1

Slide 1 text

And that’s a Wrap! Getting a taste of Property Wrappers FrenchKit October 2019 aligatr Swift 5.1 * burritos not provided *

Slide 2

Slide 2 text

aligatr FrenchKit 2019 Imagine you want this class Movie { var progress: Double = 0.0 { didSet { self.progress = max(0.0, min(progress, 1.0)) } } var volume: Double = 1.0 { didSet { self.volume = max(0.0, min(volume, 1.0)) } } } let movie = Movie() movie.progress = 42.0 print(movie.progress)

Slide 3

Slide 3 text

aligatr FrenchKit 2019 Create reusable behavior struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { var progress: Clamped var volume : Clamped } let movie = Movie() movie.progress.wrappedValue = 42.0 print(movie.progress.wrappedValue)

Slide 4

Slide 4 text

aligatr FrenchKit 2019 Create reusable behavior struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { var progress: Clamped = Clamped(wrappedValue: 0.0) var volume : Clamped = Clamped(wrappedValue: 1.0) } let movie = Movie() movie.progress.wrappedValue = 42.0 print(movie.progress.wrappedValue)

Slide 5

Slide 5 text

aligatr FrenchKit 2019 Property Wrappers to the rescue! It’s a bird! It’s a plane! It’s… wift 5.1!

Slide 6

Slide 6 text

aligatr FrenchKit 2019 Introducing Property Wrappers struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { var progress = Clamped(wrappedValue: 0.0) var volume = Clamped(wrappedValue: 1.0) } let movie = Movie() movie.progress.wrappedValue = 42.0 print(movie.progress.wrappedValue)

Slide 7

Slide 7 text

aligatr FrenchKit 2019 Introducing Property Wrappers @propertyWrapper struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { var progress = Clamped(wrappedValue: 0.0) var volume = Clamped(wrappedValue: 1.0) } let movie = Movie() movie.progress.wrappedValue = 42.0 print(movie.progress.wrappedValue)

Slide 8

Slide 8 text

aligatr FrenchKit 2019 Introducing Property Wrappers @propertyWrapper struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { @Clamped var progress = 0.0 // Double @Clamped var volume = 1.0 // Double } let movie = Movie() movie.progress = 42.0 print(movie.progress) ✨

Slide 9

Slide 9 text

aligatr FrenchKit 2019 Compiler-Generated Code // We type this @Clamped var progress = 0.0 // And compiler generates this for us private var _progress: Clamped = Clamped(wrappedValue: 0.0) var progress: Double { get { return _progress.wrappedValue } set { _progress.wrappedValue = newValue } }

Slide 10

Slide 10 text

aligatr FrenchKit 2019 Generics and Custom Range @propertyWrapper struct Clamped { var wrappedValue: Double { didSet { self.wrappedValue = max(0.0, min(wrappedValue, 1.0)) } } } class Movie { @Clamped var progress = 0.0 @Clamped var volume = 1.0 }

Slide 11

Slide 11 text

aligatr FrenchKit 2019 Generics and Custom Range @propertyWrapper struct Clamped { var wrappedValue: Value { didSet { self.wrappedValue = max(range.lowerBound, min(wrappedValue, range.upperBound)) } } let range: ClosedRange } class Movie { @Clamped var progress = 0.0 @Clamped var volume = 1.0 }

Slide 12

Slide 12 text

aligatr FrenchKit 2019 Generics and Custom Range @propertyWrapper struct Clamped { var wrappedValue: Value { didSet { self.wrappedValue = max(range.lowerBound, min(wrappedValue, range.upperBound)) } } let range: ClosedRange } class Movie { @Clamped(range: 0...1) var progress = 0.0 // Double @Clamped(range: 0...1) var volume = 1.0 // Double @Clamped(range: 0...5) var rating = 4 // Int }

Slide 13

Slide 13 text

aligatr FrenchKit 2019 UserDefaults let defaults: UserDefaults = .standard struct MainUser { var name: String { get { defaults.object(forKey: "username") as? String ?? "Olivier" } set { defaults.set(newValue, forKey: "username") } } func demo() { print(name) name = "AliGator" } }

Slide 14

Slide 14 text

aligatr FrenchKit 2019 UserDefaults let defaults: UserDefaults = .standard struct MainUser { var name: String { get { defaults.object(forKey: "username") as? String ?? "Olivier" } set { defaults.set(newValue, forKey: "username") } } func demo() { print(name) name = "AliGator" } }

Slide 15

Slide 15 text

aligatr FrenchKit 2019 UserDefaults @propertyWrapper class UserDefault { let defaults: UserDefaults = .standard var wrappedValue: T { get { defaults.object(forKey: "username") as? T ?? "Olivier" } set { defaults.set(newValue, forKey: "username") } } }

Slide 16

Slide 16 text

aligatr FrenchKit 2019 UserDefaults @propertyWrapper class UserDefault { let defaults: UserDefaults = .standard let key: String let defaultValue: T var wrappedValue: T { get { defaults.object(forKey: key) as? T ?? defaultValue } set { defaults.set(newValue, forKey: key) } } init(wrappedValue: T, key: String) { self.defaultValue = wrappedValue self.key = key } } At call site, this: @UserDefault(key: "username") var name = "Olivier"

Slide 17

Slide 17 text

aligatr FrenchKit 2019 UserDefaults struct MainUser { @UserDefault(key: "username") var name: String = "Olivier" func demo() { print(name) // object(forKey: "username") name = "AliGator" // set("AliGator", forKey: “username") } }

Slide 18

Slide 18 text

aligatr FrenchKit 2019 UserDefaults struct MainUser { @UserDefault(key: "username") var name: String = "Olivier" func demo() { print(name) // object(forKey: "username") name = "AliGator" // set("AliGator", forKey: “username") } } Compiler will generate:
 private var _name = UserDefault(wrappedValue: "Olivier", key: "username") var name: String { /* get & set proxying wrappedValue */ }

Slide 19

Slide 19 text

aligatr FrenchKit 2019 Accessing the Wrapper struct MainUser { @UserDefault(key: "username") var name: String = "Olivier" func demo() { print(name) // object(forKey: "username") name = "AliGator" // set("AliGator", forKey: “username") print("\(_name.key) = \(name)") } } Compiler will generate:
 private var _name = UserDefault(wrappedValue: "Olivier", key: "username") var name: String { /* get & set proxying wrappedValue */ }

Slide 20

Slide 20 text

aligatr FrenchKit 2019 Accessing the Wrapper @propertyWrapper struct UserDefault { /* … */ let key: String var wrappedValue: T { … } } let user = MainUser() user.name /* access the wrapper’s wrappedValue */ user._name.key /* error: _name is private */ Compiler will generate: private var _name: UserDefault var name: T {
 _name.wrappedValue
 } 
 


Slide 21

Slide 21 text

aligatr FrenchKit 2019 Projected Value @propertyWrapper struct UserDefault { /* … */ let key: String var wrappedValue: T { … } var projectedValue: String { key } } let user = MainUser() user.name /* access the wrapper’s wrappedValue */ user._name.key /* error: _name is private */ Compiler will generate: private var _name: UserDefault var name: T {
 _name.wrappedValue
 } var $name: String {
 _name.projectedValue
 }

Slide 22

Slide 22 text

aligatr FrenchKit 2019 Projected Value @propertyWrapper struct UserDefault { /* … */ let key: String var wrappedValue: T { … } var projectedValue: String { key } } let user = MainUser() user.name /* access the wrapper’s wrappedValue */ user.$name /* access the wrapper’s projectedValue */ Compiler will generate: private var _name: UserDefault var name: T {
 _name.wrappedValue
 } var $name: String {
 _name.projectedValue
 }

Slide 23

Slide 23 text

aligatr FrenchKit 2019 Some More Inspiration @Versioned var name: String = "" // and $name returns the history @Undoable var name: String = "" // and $name.undo() restores previous values @Expirable(duration: 60) var token: String? @Validated({ $0.count > 8 }) var password: String = “Fr3nchKu1te" // and $password tells if it’s valid @Rounded(digits: 2) var price: Double = 0.0 @DBField(name: “firstname") var name: String @State var user: User // $user returns the Binding @Published var age: Int // $age.sink() { … } to subscribe

Slide 24

Slide 24 text

aligatr FrenchKit 2019 One More Thing…

Slide 25

Slide 25 text

aligatr FrenchKit 2019 PropertyWrappers and Codable struct Movie: Codable { let title: String let director: String let plot: String var releaseDate: Date? } /* { "director" : "…", "title" : "…", "plot" : "…", "releaseDate" : 590372414.71850204 } */ Need to implement Codable manually for every single property…

Slide 26

Slide 26 text

aligatr FrenchKit 2019 PropertyWrappers and Codable struct Movie: Codable { let title: String let director: String let plot: String var releaseDate: Date? } /* { "director" : "…", "title" : "…", "plot" : "…", "releaseDate" : 590372414.71850204 } */ @propertyWrapper struct YMD { var wrappedValue: Date? } extension YMD: Codable { /* convert Date from/to YMD string via singleValueContainer */ } func encode(to encoder: Encoder) throws { … } init(from decoder: Decoder) throws { … }

Slide 27

Slide 27 text

aligatr FrenchKit 2019 PropertyWrappers and Codable struct Movie: Codable { let title: String let director: String let plot: String @YMD var releaseDate: Date? } /* { "director" : "…", "title" : "…", "plot" : "…", "releaseDate" : "2019-9-17" } */ @propertyWrapper struct YMD { var wrappedValue: Date? } extension YMD: Codable { /* convert Date from/to YMD string via singleValueContainer */ } func encode(to encoder: Encoder) throws { … } init(from decoder: Decoder) throws { … }

Slide 28

Slide 28 text

aligatr FrenchKit 2019 PropertyWrappers and Codable struct Movie: Codable { let title: String let director: String let plot: String @YMD var releaseDate: Date? } /* { "director" : "…", "title" : "…", "plot" : "…", "releaseDate" : "2019-9-17" } */ @propertyWrapper struct YMD { var wrappedValue: Date? } extension YMD: Codable { /* convert Date from/to YMD string via singleValueContainer */ }

Slide 29

Slide 29 text

aligatr FrenchKit 2019 And that’s a Wrap! • SE-258: The official Proposal
 https://github.com/apple/swift-evolution/ ... /0258-property-wrappers.md • SwiftLee
 https://www.avanderlee.com/swift/property-wrappers/ • NSHipster
 https://nshipster.com/propertywrapper/ • Burritos
 https://github.com/guillermomuntaner/Burritos • Re-implementing State and Binding manually to understand the wrappers
 https://gist.github.com/AliSoftware/ecb5dfeaa7884fc0ce96178dfdd326f8