Upgrade to Pro — share decks privately, control downloads, hide ads and more …

And that's a Wrap!

And that's a Wrap!

My talk about "Property Wrappers" that I gave at FrenchKit 2019.

Property Wrappers are a new feature in Swift 5.1 which lets you to create reusable get/set behaviors with custom logic and semantic meaning, and use them as annotations for your properties in Swift

AliSoftware

October 08, 2019
Tweet

More Decks by AliSoftware

Other Decks in Programming

Transcript

  1. And that’s a Wrap! Getting a taste of Property Wrappers

    FrenchKit October 2019 aligatr Swift 5.1 * burritos not provided *
  2. 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)
  3. 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)
  4. 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)
  5. aligatr FrenchKit 2019 Property Wrappers to the rescue! It’s a

    bird! It’s a plane! It’s… wift 5.1!
  6. 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)
  7. 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)
  8. 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) ✨
  9. 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 } }
  10. 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 }
  11. aligatr FrenchKit 2019 Generics and Custom Range @propertyWrapper struct Clamped<Value:

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

    Comparable> { var wrappedValue: Value { didSet { self.wrappedValue = max(range.lowerBound, min(wrappedValue, range.upperBound)) } } let range: ClosedRange<Value> } 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 }
  13. 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" } }
  14. 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" } }
  15. aligatr FrenchKit 2019 UserDefaults @propertyWrapper class UserDefault<T> { let defaults:

    UserDefaults = .standard var wrappedValue: T { get { defaults.object(forKey: "username") as? T ?? "Olivier" } set { defaults.set(newValue, forKey: "username") } } }
  16. aligatr FrenchKit 2019 UserDefaults @propertyWrapper class UserDefault<T> { 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"
  17. 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") } }
  18. 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 */ }
  19. 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 */ }
  20. aligatr FrenchKit 2019 Accessing the Wrapper @propertyWrapper struct UserDefault<T> {

    /* … */ 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<T> var name: T {
 _name.wrappedValue
 } 
 

  21. aligatr FrenchKit 2019 Projected Value @propertyWrapper struct UserDefault<T> { /*

    … */ 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<T> var name: T {
 _name.wrappedValue
 } var $name: String {
 _name.projectedValue
 }
  22. aligatr FrenchKit 2019 Projected Value @propertyWrapper struct UserDefault<T> { /*

    … */ 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<T> var name: T {
 _name.wrappedValue
 } var $name: String {
 _name.projectedValue
 }
  23. 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
  24. 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…
  25. 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 { … }
  26. 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 { … }
  27. 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 */ }
  28. 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