-
-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(blog): add swift-value-reference
- Loading branch information
1 parent
fe163fe
commit 9454631
Showing
2 changed files
with
349 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,346 @@ | ||
--- | ||
title: "Understanding Swift's Value and Reference Types" | ||
publishedAt: '2022-05-10' | ||
description: "In-depth explanation of Swift's value and reference types." | ||
englishOnly: 'true' | ||
banner: 'rafael-garcin-VR03NcIer2U-unsplash_lpjftc' | ||
tags: 'swift' | ||
--- | ||
|
||
## Introduction | ||
|
||
There are **two kinds of types** in Swift, which are Value and Reference Types. These types and their characteristics sometimes can be hard to remember and understand. Through this post, I'll try to explain it using a mental model and analogy which will help you easily master swift types. | ||
|
||
## Primitives | ||
|
||
If you use common programming languages (Java, JavaScript, etc) before, you must be familiar with **primitives and non-primitives data types**. I'm not going to jump into the details of primitives, but here are some illustrations I got from google. | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/other-primitive-types_vfmjxc' | ||
alt='other-primitive-types' | ||
width={2505} | ||
height={720} | ||
/> | ||
|
||
Usually, primitives conclude specific data types such as boolean, char, integer, and float. | ||
|
||
### Does Swift have primitive types? | ||
|
||
No, **Swift doesn't have primitive types.** In a sense. Swift **still provides 'primitive-like' data** types such as Int, Bool, Double, etc. However, they are made with **struct**. | ||
|
||
If you look into Swift's `Int` type definition, you can see that it is made with a `struct` | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/primitive-made-with-struct_k2psql' | ||
alt='primitive-made-with-struct' | ||
width={768} | ||
height={449} | ||
/> | ||
|
||
Interesting right? | ||
|
||
--- | ||
|
||
## Quick Intro to Mental Model | ||
|
||
> A mental model is an explanation of someone's **thought process** about how something works in the real world. It is a **representation of the surrounding world**. - [Wikipedia](https://en.wikipedia.org/wiki/Mental_model) | ||
You might be familiar with this variable box analogy: | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/box-analogy_ay0zra' | ||
alt='box-analogy' | ||
width={1400} | ||
height={436} | ||
/> | ||
|
||
> We think of variables as containers that hold information and allow us to access them later. We will think of this as a **box** that has a **label** on it. - [StudeApps](https://studeappsblog.medium.com/what-is-a-variable-dd7e539bf388) | ||
This works wonders when you are trying to understand what a variable does. | ||
|
||
**That is a mental model.** You create a certain type of analogy to help you understand a concept. | ||
|
||
The prior analogy is not a one-size-fits-all, I won't be using it to explain value & reference type. So prepare for some changes 💪 | ||
|
||
--- | ||
|
||
## Value and Reference Types | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/types-in-swift_culrrx' | ||
alt='types-in-swift' | ||
width={1302} | ||
height={596} | ||
/> | ||
|
||
There are two kinds of types in Swift which are **Value Types**, and **Reference Types.** Value types are usually defined as `struct`, `enum`, and `tuple`. Whereas the latter is usually defined as a `class` | ||
|
||
### Wire Analogy | ||
|
||
I'm going to use a new mental model for variables, which uses a wire to **point** to the value it holds. | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/wire-analogy_b0nblr' | ||
alt='wire-analogy' | ||
width={1155} | ||
height={497} | ||
/> | ||
|
||
Therefore each variable can point to a single value according to its data type. | ||
|
||
--- | ||
|
||
## Value Types | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/value-types_ofai6i' | ||
alt='value-types' | ||
width={736} | ||
height={550} | ||
/> | ||
|
||
> A value type is a type whose value is **copied** when it's assigned to a variable or constant, or when it's passed to a function - [Swift Docs](https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html) | ||
Remember that 'primitive' data types like Int, Double, String, etc. are made with **struct.** So they follow the value type mental model. | ||
|
||
### Mental Model | ||
|
||
Let's say we have a struct of Animal (the behavior is also the same with enum, tuple, also Int, String because they're made with struct) | ||
|
||
```swift | ||
struct Animal { | ||
var legs = 4 | ||
} | ||
|
||
var sheep = Animal() | ||
``` | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/types-model-1_jwbgaz' | ||
alt='types-model-1' | ||
width={577} | ||
height={190} | ||
/> | ||
|
||
Then, we are assigning the `cow` variables with the value of `sheep` | ||
|
||
```swift | ||
var cow = sheep | ||
``` | ||
|
||
Key point: **the value will be copied**. | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/types-model-2_uguz9k' | ||
alt='types-model-2' | ||
width={612} | ||
height={695} | ||
/> | ||
|
||
### Effect of Copying | ||
|
||
After we copy, the `sheep` and `cow` variables now points to **two different struct.** Therefore if we mutate the `cow`, the `sheep` **won't get affected**, and vice versa. | ||
|
||
```swift {8,9,11,12} | ||
struct Animal { | ||
var legs = 4 | ||
} | ||
|
||
var sheep = Animal() | ||
var cow = sheep | ||
|
||
// mutating cow's property | ||
cow.legs = 3 | ||
|
||
print(sheep.legs) // 4 | ||
print(cow.legs) // 3 | ||
``` | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/types-copy-effect_g8v4gn' | ||
alt='types-copy-effect' | ||
width={751} | ||
height={458} | ||
/> | ||
|
||
## Reference Types | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/reference-types_cwxcvo' | ||
alt='reference-types' | ||
width={723} | ||
height={647} | ||
/> | ||
|
||
> A reference types is where instances **share a single copy** of the data when they're assigned to a variable or constant, or when they're passed to a function. | ||
In the wire analogy, **it will point to the same value**. We're using a class that behaves as a reference type. | ||
|
||
### Mental Model | ||
|
||
```swift | ||
class Animal { | ||
var legs = 4 | ||
} | ||
|
||
var sheep = Animal() | ||
var cow = sheep | ||
``` | ||
|
||
Key Point: **It will share a single copy** | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/reference-model_oj5b3u' | ||
alt='reference-model' | ||
width={584} | ||
height={659} | ||
/> | ||
|
||
### Proof | ||
|
||
To prove that it is sharing a single copy, we can use `===` ([identity equality](https://developer.apple.com/documentation/swift/1538988)). It will return true if two reference point to the same object instance. | ||
|
||
Let's throw in a new instance called `pig` | ||
|
||
```swift | ||
var sheep = Animal() | ||
var cow = sheep | ||
|
||
// created a new instance | ||
var pig = Animal() | ||
``` | ||
|
||
Here's the wire | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/reference-proof_pkg0tg' | ||
alt='reference-proof' | ||
width={549} | ||
height={448} | ||
/> | ||
|
||
Then we can compare them using identity equality | ||
|
||
```swift | ||
print(sheep === cow) // true | ||
print(sheep === newSheep) // false | ||
``` | ||
|
||
When in doubt, draw the wire analogy to help you. I'm using [excalidraw](https://excalidraw.com/) for the illustration | ||
|
||
### Effect of Sharing A Single Copy | ||
|
||
I believe you already guessed correctly how it will behave. If we **mutate** one variable, **both will be affected**. | ||
|
||
```swift {8,9,11,12} | ||
class Animal { | ||
var legs = 4 | ||
} | ||
|
||
var sheep = Animal() | ||
var cow = sheep | ||
|
||
// mutating cow's property | ||
cow.legs = 3 | ||
|
||
print(sheep.legs) // 3 | ||
print(cow.legs) // 3 | ||
``` | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/reference-copy-effect_khsni9' | ||
alt='reference-copy-effect' | ||
width={824} | ||
height={444} | ||
/> | ||
|
||
## Additional Emphasize | ||
|
||
I need to emphasize this in case you're coming from **a JavaScript** background. | ||
|
||
In Swift, **Array and Dictionary are all value types**. | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/array-struct_xfz7eu' | ||
alt='array-struct' | ||
width={772} | ||
height={332} | ||
/> | ||
|
||
It is still made with struct 😬 | ||
|
||
## How to Choose? | ||
|
||
I don't have much experience with this yet, so I'll [quote an article](https://developer.apple.com/swift/blog/?id=10) instead | ||
|
||
Use a value type when: | ||
|
||
- Comparing instance data with `==` makes sense | ||
- You want copies to have an independent state | ||
- The data will be used in code across multiple threads | ||
|
||
Use a reference type (e.g. use a class) when: | ||
|
||
- Comparing instance identity with `===` makes sense | ||
- You want to create a shared, mutable state | ||
|
||
I believe that using value type for overall use will be sufficient. We can trust that when we change one variable/property, it won't affect the others. Thus, creating **a sense of safety and reliability**. | ||
|
||
Keep a note that this difference only happens when you mutate. **In absence of mutation, values and references act exactly the same way.** | ||
|
||
## Functions & In-Out | ||
|
||
Function parameter follows **value types.** This means you can't mutate the parameter and change the value. | ||
|
||
<CloudinaryImg | ||
mdx | ||
publicId='theodorusclarence/blogs/swift-value-reference/function-unable-to-mutate_jk9sbh' | ||
alt='function-unable-to-mutate' | ||
width={744} | ||
height={116} | ||
/> | ||
|
||
**Swift won't even let you mutate them.** Because what is passed in the parameter will be converted into a `let` variable. | ||
|
||
You can **imitate reference types** on function parameter by using `inout` | ||
|
||
```swift | ||
var numbers = [1,2,3] | ||
|
||
func foo(_ arr: inout Array<Int>) { | ||
arr.removeLast() | ||
} | ||
|
||
foo(&numbers) | ||
print(numbers) // [1,2] | ||
``` | ||
|
||
Notice the `&`(ampersand) which is an explicit recognition that you're aware it is being used as `inout`. | ||
|
||
Under the hood, the In-Out parameter **doesn't use reference types.** | ||
|
||
> This behavior is known as _copy-in copy-out_ or _call by value result_. For example, when a computed property or a property with observers is passed as an in-out parameter, its getter is called as part of the function call and its setter is called as part of the function return. - [Swift Docs](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID545) | ||
## Conclusion | ||
|
||
You now understand that: | ||
|
||
- Swift 'primitive-like' variables are made with a struct | ||
- Value types will copy the value if assigned to a variable or passed into a function | ||
- Reference types will share a single instance if assigned to a variable or passed into a function | ||
- Mutating value types won't affect the other copy, on the other hand, mutating reference types will affect the single instance | ||
- Function parameters follows value types, but can imitate reference types by using the in-out parameter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9454631
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
theodorusclarence – ./
theodorusclarence-git-main-theodorusclarence.vercel.app
theodorusclarence-theodorusclarence.vercel.app
theodorusclarence.com