Value Types
A value type instance is an independent instance and holds its data in its own memory allocation. There are a few different value types: Struct, Enum, Tupple.
Struct
Let’s experiment with struct
and prove that they’re value types:
Add the following code to your playground:
struct Car {
let brand: String
var model: String
}
var golf = Car(brand: "Volkswagen", model: "Golf")
let polo = golf
golf.model = "Golf 2019"
print(golf)
print(polo)
In the code above, you will:
- Create a
Car
struct with brand
and model
properties. - Create a new instance of
Car
named golf
. - Create a copy of the
golf
instance, named polo
. - Change the
golf.model
variable to Golf 2019
- Print the 2 different instances. The first
print
statement prints Car(brand: "Volkswagen", model: "Golf 2019")
in the Console. The second one prints Car(brand: "Volkswagen", model: "Golf")
. Even if polo
is a copy of golf
, the instances remain independent with their own unique data copies.
With this simple playground, we’ve confirmed that structs
are indeed value types.
Enum
To check that enums
are value types, add this code to the playground:
enum Language {
case italian
case english
}
var italian = Language.italian
let english = italian
italian = .english
print(italian)
print(english)
In the code above, you will:
- Create a
Language
enum with italian
and english
cases. - Create a new instance of
Language
for the italian
language. - Create a copy of the
italian
instance, named english
. - Change the
italian
instance to english
. - Print the two different instances. The first
print
statement prints english
, and the second one prints italian
. Even if english
is a copy of italian
, the instances remain independent.
Tuple
The last value type that we'll explore is tuple
. A tuple type is a comma-separated list of zero or more types, enclosed in parentheses. You can access its values using the dot (.
) notation followed by the index of the value.
You can also name the elements in a tuple and use the names to access the different values.
Add the following code to the playground:
var ironMan = ("Tony", "Stark")
let parent = ironMan
ironMan.0 = "Alfred"
print(ironMan)
print(parent)
In the code above, you will:
- Create an
ironMan
tuple with the strings Tony
and Stark
. - Create a copy of the
ironMan
instance, named parent
. - Change the
ironMan.0
index to Alfred
. - Print the 2 different instances. The first
print
, prints ("Alfred", "Stark")
and the second one, prints ("Tony", "Stark")
. Again, the instances remain independent.
You can now be certain that structs
, enums
, and tuples
are value types!
When to Use Value Types
Use value types when comparing instance data with ==
makes sense.
==
checks if every property of the two instances is the same.
With value types you always get a unique, copied instance, and you can be sure that no other part of your app is changing the data under the hood. This is especially helpful in multi-threaded environments where a different thread could alter your data.
Use a value type when you want copies to have an independent state, and the data will be used in code across multiple threads.
In Swift, Array
, String
, and Dictionary
are all value types.
Reference Types
In Swift, reference type instances share a single copy of their data, so that every new instance will point to the same address in memory. A typical example is a class
, function
, or closure
.
To explore these, add a new function to your playground:
func address<T: AnyObject>(of object: T) -> Int {
return unsafeBitCast(object, to: Int.self)
}
This function prints the address of an object, which will help you check whether you're referencing the same instance or not.
Class
The first reference type that you'll look at is a class
.
Add the following code to your playground:
class Dog: CustomStringConvertible {
var age: Int
var weight: Int
var description: String {
return "Age \(age) - Weight \(weight)"
}
init(age: Int, weight: Int) {
self.age = age
self.weight = weight
}
}
let doberman = Dog(age: 1, weight: 70)
let chihuahua = doberman
doberman.age = 2
chihuahua.weight = 10
print(doberman)
print(chihuahua)
print(address(of: doberman))
print(address(of: chihuahua))
In the code above, you will:
- Create a new
class
named Dog
, that conforms to CustomStringConvertible
to print the custom descriptions of the object. - Define the custom description of the object.
- Create a new
init
function. This is needed because, unlike a struct
, a class
doesn't automatically create an initialization function based on the variables of the object. - Create a
doberman
instance of Dog
. - Create a copy of
doberman
, named chihuahua
. - Change the
doberman.age
to 2
. - Change the
chihuahua.weight
to 10
. - Print the description of the two different instances. The first
print
, prints Age 2 - Weight 10
, and the second one prints the same; Age 2 - Weight 10
. This is because you're actually referencing the same object. - Print the address of the two different instances. With these prints, you'll be sure that you're referencing the same address. You'll see that both
print
statements print the same value.
You can rest assured that a class
is a reference type.
Functions and Closures
A closure
is used to refer to a function along with the variables from its scope that it encapsulates. Functions are essentially closures that store references to variables in their context.
Take a look at the code below:
let closure = { print("Test") }
func function() -> (){ print("Test") }
closure()
function()
They both do the same thing.
You can find more info about closures in Swift's docs.
When to Use Reference Types
Use a reference type when comparing instance identity with ===
makes sense. ===
checks if two objects share the same memory address.
They’re also useful when you want to create a shared, mutable state.
As a general rule, start by creating your instance as an enum
, then move to a struct
if you need more customization, and finally move to class
when needed.