DEV Community

loading...

Property Wrappers

Juan Dorado
Passionate iOS dev! Share what you learn is my motto! so all we can still growing!
・3 min read

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"

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"

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
}

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"

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

Take a look at this.

private var owner: Person = Person()

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

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"

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

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

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
}

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! 👨‍💻

Discussion (0)