ref – https://www.raywenderlich.com/112027/reference-value-types-in-swift-part-1
Value types keep a unique copy of their data, while reference types share a single copy of their data.
Reference type
Swift represents a reference type as a class. This is similar to Objective-C, where everything that inherits from NSObject is stored as a reference type.
In Objective-C — and most other object-oriented languages — you hold references to objects. In Swift, however, you use class which is implemented using reference semantics.
1 2 3 4 5 |
// Reference Types: class Dog { var wasFed = false } |
The above class represents a pet dog and whether or not the dog has been fed. Create a new instance of your Dog class by adding the following:
1 |
let dog = Dog() |
This simply points to a location in memory that stores dog. To add another object to hold a reference to the same dog, add the following:
1 |
let puppy = dog |
Because dog is a reference to a memory address, puppy points to the exact same address. Feed your pet by setting wasFed to true:
1 |
puppy.wasFed = true |
Therefore you’d expect any change in one to be reflected in the other. Check that this is true by viewing the property values in your playground:
1 2 |
dog.wasFed // true puppy.wasFed // true |
Changing one named instance affects the other since they both reference the same object. This is exactly what you’d expect in Objective-C.
Value types
There are many kinds of value types in Swift, such as struct, enum, and tuples. You might not realize that Objective-C also uses value types in number literals like NSInteger or even C structures like CGPoint.
1 2 3 4 5 6 |
var a = 42 var b = a b+=1 a // 42 b // 43 |
Clearly, a equals 42 and b equals 43. If you’d declared them as reference types instead, both a and b would equal 43 since both would point to the same memory address.
1 2 3 4 5 6 7 8 9 10 |
struct Cat { var wasFed = false } var cat = Cat() var kitty = cat // data copied kitty.wasFed = true cat.wasFed // false kitty.wasFed // true |
This shows a subtle, but important difference between reference and value types: setting kitty‘s wasFed property has no effect on cat. The kitty variable received a copy of the value of cat instead of a reference.
Although it’s much faster to assign a reference to a variable, copies are almost as cheap. Copy operations run in constant O(n) time since they use a fixed number of reference-counting operations based on the size of the data.
Mutability
For reference types, let means the reference must remain constant. However, you can still dereference them and change the data. In other words, you can’t change the instance the constant references, but you can mutate the instance itself.
For value types let means the instance must remain constant. No properties of the instance will ever change, regardless whether the property is declared with let or var.
It’s much easier to control mutability with value types. To achieve the same immutability/mutability behavior with reference types, you’d need to implement immutable and mutable class variants such as NSString and NSMutableString.
Which to Use and When
When to Use a Value Type
Generally speaking, use value types in the following instances:
Comparing instance data with == makes sense
“But of course,” you say. “I want every object to be comparable!”. But you need to consider whether the data should be comparable. Consider the following implementation of a point:
1 2 3 4 5 6 7 8 |
struct Point: CustomStringConvertible { var x: Float var y: Float var description: String { return "{x: \(x), y: \(y)}" } } |
Does that mean two variables with the exact same x and y members should be considered equal?
1 2 |
let point1 = Point(x: 2, y: 3) let point2 = Point(x: 2, y: 3) |
It’s clear that these two Points with the same internal values should be considered equal. The memory location of those values doesn’t matter; you’re concerned about the values themselves.
Therefore, you’d need to conform to the Equatable protocol, which is good practice for all value types. This protocol defines only one function which you must implement globally in order to compare two instances of the object. This means that the == operator must have the following characteristics:
Reflexive: x == x is true
Symmetric: if x == y then y == x
Transitive: if x == y and y == z then x == z
If you have a custom struct, you’d implement the equal sign like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct RickyPoint { var x : Float var y : Float var description: String { return "{ x: \(x), y: \(y)}" } } // when you conform to protocol Equatable, you must implement: // public static func ==(lhs: Self, rhs: Self) -> Bool // else, you will get an error that you do not conform to protocol Equatable extension RickyPoint : Equatable { public static func == (lhs: RickyPoint, rhs: RickyPoint) -> Bool { return ((lhs.x == rhs.x) && (lhs.y == rhs.y)) } } |
Copies should have independent state
Structs, being value types, have independent state.
1 2 3 4 5 |
struct Shape { var center: Point } let initialPoint = Point(x: 0, y: 0) |
Let’s instantiate two Shape structs.
1 2 |
let circle = Shape(center: initialPoint) var square = Shape(center: initialPoint) |
If we are to change their state, it would be unique.
1 2 |
square.center.x = 5 // {x: 5.0, y: 0.0} circle.center // {x: 0.0, y: 0.0} |
Each Shape needs its own copy of a Point so you can maintain their state independent of each other.
The data will be used in code across multiple threads
This one’s a little more complex. Will multiple threads access this data? If so, will it really matter if the data isn’t equal across all threads at all times?
To make your data accessible from multiple threads and equal across threads, you’ll need:
1) to use a reference type and
2) implement locking as well
Thus, if threads can uniquely own the data, using value types makes the whole point moot since each owner of the data holds a unique copy rather than a shared reference.
When to Use a Reference Type
Although value types are useful in many cases, reference types are still useful in the following situations:
Comparing instance identity with === makes sense (this is where it compares the reference address)
=== checks if two objects are exactly identical, right down to the memory address that stores the data.
To put this in real-world terms, consider the following: if your cubicle-mate swaps one of your $20 bills with another legitimate $20 bill, you don’t really care, as you’re only concerned about the value of the object.
However, if someone stole the Magna Carta and created an identical parchment copy of the document in its place, that would matter greatly because the inherent identity of the document is not the same at all.
You can use the same thought process when deciding whether to use reference types; usually there are very few times when you really care about the inherent identity — that is, the memory location — of the data. You usually just care about comparing the data values.
You want to create a shared, mutable state
Sometimes you want a piece of data to be stored as a single instance and accessed and mutated by multiple consumers.
A common object with a shared, mutable state is a shared bank account. You might implement a basic representation of an account and person as follows:
1 2 3 4 5 6 7 8 9 10 |
class Account { var balance = 0.0 } class Person { let account: Account init(_ account: Account) { self.account = account } } |
If any joint account holders add money to the account, then the new balance should be reflected on all debit cards linked to the account:
1 2 3 4 5 6 7 8 9 |
let account = Account() let person1 = Person(account) let person2 = Person(account) person2.account.balance += 100.0 person1.account.balance // 100 person2.account.balance // 100 |
Since Account is a class, each Person holds a reference to the account, and everything stays in sync.