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! 👨💻
Top comments (0)