DEV Community

Juan Dorado
Juan Dorado

Posted on

Property Wrappers

If you have work with SwiftUI, you may see these couple of properties that begin with @State.

Well, this works as a property wrapper!.

Let's dive in, to get involved with this kind of properties we must know about value semantics.

Value Semantics

When your type supports value semantics, it means that you are watching that variable specifically, it guarantees the independence of variables.

These are related to value types (ex. structs).

Reference types and Value types differ in their assignment behavior, with reference we are pointing to the same reference, hash, address, unlike value we are just creating a new copy of our instance.

Reference Type

class ReferencePerson {

  var name: String

  init(name: String) {
    self.name = name
  }
}

var referencePerson1 = ReferencePerson(name: "Juan")
var referencePerson2 = referencePerson1

referencePerson2.name = "Ethan"

referencePerson1.name // output: "Ethan"
referencePerson2.name // output: "Ethan"
Enter fullscreen mode Exit fullscreen mode

Value Type

struct ValuePerson {

  var name: String
}

var valuePerson1 = ValuePerson(name: "Juan")
var valuePerson2 = valuePerson1

valuePerson2.name = "Ethan"

valuePerson1.name // output: "Juan"
valuePerson2.name // output: "Ethan"
Enter fullscreen mode Exit fullscreen mode

Value Types containing Reference Types

We could combine these two types on one, so we would expect that if we are containing a reference inside a value type, the assignment should be copy.

Let's take an example.

class Person {

  var name: String

  init(name: String) {
    self.name = name
  }
}

struct Dog {

  var name: String
  var owner: Person
}
Enter fullscreen mode Exit fullscreen mode

Let's use these two classes, as you can see Dog is of type struct and Person is of type class.

var husky = Dog(name: "Jay-Jay", owner: Person(name: "Mike"))
var pug = husky

pug.owner.name = "Michell"

husky.owner.name // output: "Michell"
pug.owner.name // output: "Michell"
Enter fullscreen mode Exit fullscreen mode

Oh-oh!, what happened? - Well, although we are creating a copy, our Person is pointing to the same reference.

But we have a solution to solve this, so here is where we meet Copy-on-write!

Copy-on-write

With copy-on-write we can validate what we really need to have value semantics, at the example above, the owner's name.

So, let's modify our Dog class.

struct Dog {

  var name: String

  private var owner: Person = Person()

  var ownerName: String {
    get { owner.name }
    set { owner = Person(name: newValue) }
  }

  init(name: String, ownerName: String) {
    self.name = name
    self.ownerName = ownerName
  }
}
Enter fullscreen mode Exit fullscreen mode

Take a look at this.

private var owner: Person = Person()

  var ownerName: String {
    get { owner.name }
    set { owner = Person(name: newValue) }
  }
Enter fullscreen mode Exit fullscreen mode

This is the magic of copy on write, now if we try to instantiate our values.

var husky = Dog(name: "Jay-Jay", ownerName: "Mike")
var pug = husky

pug.ownerName = "Michell"

husky.ownerName // output: "Mike"
pug.ownerName // output: "Michell"
Enter fullscreen mode Exit fullscreen mode

The idea is as simple as just create a new backup and drop the old one, but there is something more to help with performance.

Of course there is much more under the hood about using Copy-on-write than just create a backup and drop the old one.

What if we are not referencing nothing more? - For the example before, if we just have the husky instance doesn't make sense to be creating new instances each time we change the value, for that part we have isKnownUniquelyReferenced(_ object:) this is a generic function that checks if our instance is unique or it has other references, so let's change our set from our ownerName property like this.

var ownerName: String {
    get { owner.name }
    set {
      if isKnownUniquelyReferenced(&owner) {
        owner.name = ownerName
      } else {
        owner = Person(name: newValue)
      }
    }
}
Enter fullscreen mode Exit fullscreen mode

Property Wrappers - in action

Now, you could be thinking, ok... now I know some about Value Semantics, about copy-on-write, what about Property wrappers?.

To introduce these properties let's create our Property Wrapper of the magic code of copy-on-write.

@propertyWrapper
struct CopyOnWriteOwnerName {

  private var person: Person

  var wrappedValue: String {
    get { person.name }
    set {
      if isKnownUniquelyReferenced(&person) {
        person.name = newValue
      } else {
        person = Person(name: newValue)
      }
    }
  }

  init(wrappedValue: String) {
    self.person = Person(name: wrappedValue)
  }
}
Enter fullscreen mode Exit fullscreen mode

Do you see that is the same code? - Well, now that we have encapsulated our copy-on-write code, let's use it, thanks to @propertyWrapper

We can use it inside our Dog struct, like this.

struct Dog {

  var name: String
  @CopyOnWriteOwnerName var ownerName: String
}
Enter fullscreen mode Exit fullscreen mode

And now! our code was encapsulated, it is now short, and much easier to understand!.

Do you get now what property wrapper is?

👨‍💻 Happy coding devs! 👨‍💻

Top comments (0)