logo
hero image

Memory Management pada Swift

Memahami Automatic Reference Counting (ARC), Strong Reference Cycle, dan Capture List pada Swift
5 January 2022 ยท 12 Minutes

Ketika kita membuat sebuah aplikasi khususnya aplikasi Mobile, Manajemen Memori merupakan salah satu hal yang harus kita perhatikan, karena kesalahan dalam Manajemen Memori bisa menyebabkan Memory Leak, akibatnya performa aplikasi kita menjadi sangat lambat, bahkan bisa mengakibatkan crash tak terduga.

Oleh karena itulah pada artikel kali ini kita akan belajar mengenai Manajemen Memori pada bahasa pemrograman Swift, meskipun topik ini terdengar agak advanced, tapi menurut saya Manajemen Memori itu cukup penting untuk dipelajari sejak awal karena selain alasan-alasan di atas, Manajemen Memori juga akan sering kita jumpai dalam pembuatan aplikasi nantinya.


Apa itu Memory Management ?

Pada saat aplikasi kita dijalankan, sebuah file executable yang berisi instruksi-instruksi bagaimana aplikasi kita berkerja akan dimuat ke dalam RAM. pada saat itu juga lah aplikasi kita akan mendapatkan bagian (chunk) dari RAM. bagian tersebut bernama Heap, di situ lah tempat dimana instances dari class-class kita berada ketika aplikasi kita sedang berjalan.

Jadi, apa yang dimaksud dengan Manajemen Memori? Manajemen Memori adalah proses bagaimana kita memanajemen memori Heap. proses tersebut mencakup bagaimana kita memanajemen object-object pada memori Heap dan memastikan bahwa object-object tersebut ter-deallocated dari ruang memori ketika sudah tidak dipakai lagi, sehingga ruang tersebut bisa dipakai lagi nantinya.


Mengenal Automatic Reference Counting (ARC)

Pada bahasa pemrograman Swift, untuk manajemen memorinya itu menggunakan konsep yang bernama Automatic Reference Counting (ARC), konsep ARC ini sebenarnya cukup sederhana, yaitu melacak instance pada suatu class dan memutuskan kapan waktu yang tepat untuk melakukan deallocate, ARC melacaknya dengan cara menghitung berapa banyak references (reference count) pada tiap instance, jika nilainya telah mencapai 0, artinya instance tersebut sudah tak dipakai lagi, maka instance tersebut pun akan di-deallocate.

Agar lebih jelas, perhatikan kode berikut :

Automatic Reference Counting (ARC)
icon
1class Book {
2 let title: String
3
4 init(title: String, author: String) {
5 self.title = title
6 }
7
8 deinit {
9 print("\(title) instance deallocated.")
10 }
11}
12
13var myBook1: Book? = Book(title: "Sapiens")
14var myBook2: Book? = myBook1
15
16myBook1 = nil
17myBook2 = nil

Penjelasan kode :

  1. Pertama, Kita membuat instance dari class Book, dan kita tampung ke dalam variabel myBook1, reference count menjadi 1.

icon
1var myBook1: Book? = Book(title: "Sapiens")
  1. Kita membuat reference dari myBook1 dan menampungnya ke dalam variabel myBook2, jadi, variabel myBook1 dan myBook2 mengacu pada instance Book yang sama. sekarang, reference count menjadi 2.

icon
1var myBook2: Book? = myBook1
  1. Saat kita set nilai myBook1 menjadi nil, reference count berkurang menjadi 1.

icon
1myBook1 = nil
  1. Terakhir, kita set nilai myBook2 menjadi nil juga. dan instance Book tersebut pun akan di-deallocate, karena sekarang reference count nilainya sudah mencapai 0.

icon
1myBook2 = nil

Untuk memastikan bahwa instance tersebut benar-benar ter-deallocated ketika kita set nilai myBook2 menjadi nil, silakan jalankan kodenya, maka kita akan melihat output seperti ini :

Output
1Sapiens instance deallocated.

Sebelum adanya Automatic Reference Counting (ARC), yaitu pada Xcode versi 4.2 ke bawah, kita harus melakukan increment dan decrement reference count secara manual, konsep tersebut bernama Manual Retain Release (MRR).


Mengenal dan Menangani Strong Reference Cycle

Sebelum kita berbicara mengenai Strong Reference Cycle, kita harus berkenalan terlebih dahulu dengan Strong reference.

Jadi, Strong reference adalah salah satu tipe reference yang sifatnya mencegah Class Instance untuk di-deallocate selama reference tersebut masih ada. selain Strong reference, ada 2 tipe reference lain yaitu Weak dan Unowned yang akan kita bahas nanti. Ketika kita membuat reference baru, secara default tipe reference-nya adalah Strong. Contoh Strong reference bisa kita lihat pada kode di atas bagian ini :

Automatic Reference Counting (ARC)
icon
1class Book {
2 let title: String
3
4 init(title: String, author: String) {
5 self.title = title
6 }
7
8 deinit {
9 print("\(title) instance deallocated.")
10 }
11}
12
14var myBook1: Book? = Book(title: "Sapiens")
15var myBook2: Book? = myBook1
17
18myBook1 = nil
19myBook2 = nil

Seperti yang telah kita ketahui, Strong reference sifatnya mencegah Class Instance untuk di-deallocate selama reference tersebut masih ada, dan itu bisa menyebabkan salah satu masalah Memory Leak yang bernama Strong Reference Cycle.

Strong Reference Cycle terjadi ketika ARC gagal melakukan deallocated suatu Class Instance karena ada 2 reference yang saling mempunyai tipe reference Strong (Ingat, tipe reference Strong akan mencegah suatu Class Instance ter-deallocated selama reference tersebut masih ada). agar lebih jelas saya akan memberikan beberapa contoh kasus Strong Reference Cycle yang mungkin akan kita jumpai dalam pembuatan aplikasi nantinya :

1. Dependencies

Strong Reference Cycle bisa disebabkan oleh 2 Class Instance yang saling mempunyai tipe reference Strong. Pada kode di bawah, class Book mempunyai dependency Author, dan sebaliknya Author mempunyai dependency Book.

Dependencies
icon
1class Author {
2 let name: String
3 var book: Book?
4
5 init(name: String) {
6 self.name = name
7 print("\(name) instance initialized.")
8 }
9
10 deinit {
11 print("\(name) instance deallocated.")
12 }
13}
14
15class Book {
16 let title: String
17 var author: Author?
18
19 init(title: String) {
20 self.title = title
21 print("\(title) instance initialized.")
22 }
23
24 deinit {
25 print("\(title) instance deallocated.")
26 }
27}
28
29var yuval: Author? = Author(name: "Yuval")
30var sapiens: Book? = Book(title: "Sapiens")
31
32sapiens?.author = yuval
33yuval?.book = sapiens

Ketika kita set nilainya menjadi nil, method deinit() tidak akan terpanggil. karena saat kita set nilainya menjadi nil, reference count masing-masing Class Instance nilainya belum mencapai 0.

Karena awalnya kan ada 2 reference (reference dari membuat variabel dan reference dari set property), lalu Class Instance tersebut kita set nilainya menjadi nil, dan reference count sekarang bernilai 1. karena belum mencapai 0, Class Instance tersebut pun tidak ter-deallocate dari ruang memori dan method deinit() pun tidak terpanggil.

icon
1var yuval: Author? = Author(name: "Yuval")
2var sapiens: Book? = Book(title: "Sapiens")
3
4sapiens?.author = yuval
5yuval?.book = sapiens
6
8yuval = nil
9sapiens = nil
Output
1Yuval instance initialized.
2Sapiens instance initialized.

Strong Reference Cycle - DependenciesStrong Reference Cycle - Dependencies

Solusi :

Untuk mengatasi masalah tersebut, caranya sangat mudah! kita tinggal ubah tipe reference dari yang awalnya Strong menjadi Weak atau Unowned. Berbeda dengan Strong, tipe reference Weak atau Unowned tidak melakukan increment terhadap reference count, sehingga jika kita set variabel yuval dan sapiens menjadi nil, Class Instance Book dan Author pun akan ter-deallocated.

icon
1class Book {
2 let title: String
4 weak var author: Author?
5
6 init(title: String) {
7 self.title = title
8 print("\(title) instance initialized.")
9 }
10
11 deinit {
12 print("\(title) instance deallocated.")
13 }
14}

Ohya kita hanya perlu ubah menjadi Weak reference salah satunya saja. pada contoh di atas kita ubah reference author pada class Book menjadi Weak. jadi, ketika variabel yuval kita set nilainya menjadi nil, maka Class Instance yuval akan ter-deallocated, sekarang nilai dari sapiens?.author juga akan berubah menjadi nil.

Weak Reference
icon
1print(sapiens?.title)
2print(sapiens?.author)
Output
1Optional("Sapiens")
2nil

dan ketika kita juga set nilai dari variabel sapiens menjadi nil, maka Class Instance sapiens juga akan ter-deallocated. ๐Ÿฅณ ๐ŸŽ‰

Output
1Yuval instance initialized.
2Sapiens instance initialized.
4Yuval instance deallocated.
5Sapiens instance deallocated.

Weak ReferenceWeak Reference

Apabila kita ubah tipe reference nya menjadi Unowned, itu mirip seperti Implicitly Unwrapped Optionals, jadi, kita tidak perlu lagi menggunakan Optional Chaining.

Unowned Reference
icon
1class Author {
2 let name: String
3 var book: Book!
4
5 init(name: String) {
6 self.name = name
7 print("\(name) instance initialized.")
8 }
9
10 deinit {
11 print("\(name) instance deallocated.")
12 }
13}
14
15class Book {
16 let title: String
18 unowned var author: Author
19
20 init(title: String, author: Author) {
21 self.title = title
22 self.author = author
23 print("\(title) instance initialized.")
24 }
25
26 deinit {
27 print("\(title) instance deallocated.")
28 }
29}
30
32var yuval: Author? = Author(name: "Yuval")
33yuval!.book = Book(title: "Sapiens", author: yuval!)
34
35yuval = nil
Output
1Yuval instance initialized.
2Sapiens instance initialized.
4Yuval instance deallocated.
5Sapiens instance deallocated.

Unowned ReferenceUnowned Reference

Namun jika reference author pada class Book ter-deallocated, dan kita mengakses property author tersebut, maka program kita akan crash saat dijalankan! hati-hati ๐Ÿ˜ฑ

icon
1var yuval: Author? = Author(name: "Yuval")
2var sapiens: Book = Book(title: "Sapiens", author: yuval!)
3
4yuval!.book = sapiens
5
6yuval = nil
8print(sapiens.author)

Runtime ErrorRuntime Error

2. Delegation Pattern

Delegation Pattern sering kita jumpai pada Apple Platforms, contohnya pada UITableView atau UICollectionView.

Delegation Pattern pada UITableView
icon
1import UIKit
2
3class ViewController: UIViewController {
4 @IBOutlet var tableView: UITableView! {
5 didSet {
7 tableView.delegate = self
8 tableView.dataSource = self
9 }
10 }
11}

Tujuan utama dari Delegation Pattern adalah agar suatu object dapat berkomunikasi dengan Owner Class-nya secara decoupled atau terpisah, serta tanpa perlu tahu Concrete Type yang dimiliki oleh Owner Class-nya, sehingga kode kita menjadi lebih Reusable dan Maintainable.

Namun jika kita kurang tepat dalam menggunakan Delegation Pattern, aplikasi yang kita buat bisa terkena Memory Leak!

Perhatikan kode berikut :

Delegation Pattern
icon
1protocol ExampleDelegate: AnyObject {}
2
3class FirstVC {
5 var delegate: ExampleDelegate?
6
7 init() {
8 print("FirstVC instance initialized.")
9 }
10
11 deinit{
12 print("FirstVC instance deallocated.")
13 }
14}
15
16class SecondVC: ExampleDelegate {
17 let vc = FirstVC()
18
19 init() {
21 vc.delegate = self
22 print("SecondVC instance initialized.")
23 }
24
25 deinit{
26 print("SecondVC instance deallocated.")
27 }
28}
29
30var secondVC: SecondVC? = SecondVC()

Ketika kita set nilai dari variabel secondVC menjadi nil, harapannya kan instance SecondVC dan FirstVC akan ter-deallocated oleh ARC kan? karena sudah tidak digunakan lagi. namun kalau kita jalankan kode nya, method deinit() tidak terpanggil dua-duanya, ๐Ÿค”

icon
1secondVC = nil

Kenapa? karena pada class SecondVC kita membuat Strong reference terhadap FirstVC,

SecondVC
icon
1let vc = FirstVC()
FirstVC
icon
1var delegate: ExampleDelegate?

juga sebaliknya, pada class FirstVC kita membuat Strong reference terhadap SecondVC, dannn.. kita pun terjebak dalam Strong Reference Cycle! ๐Ÿ˜ฑ

Strong Reference Cycle - Delegation PatternStrong Reference Cycle - Delegation Pattern

Solusi :

Untuk memecahkan masalah tersebut, caranya sangat mudah, kita tinggal ganti tipe reference pada property delegate menjadi weak.

Delegation Pattern
icon
1protocol ExampleDelegate: AnyObject {}
2
3class FirstVC {
5 weak var delegate: ExampleDelegate?
6
7 init() {
8 print("FirstVC instance initialized.")
9 }
10
11 deinit{
12 print("FirstVC instance deallocated.")
13 }
14}
15
16class SecondVC: ExampleDelegate {
17 let vc = FirstVC()
18
19 init() {
20 vc.delegate = self
21 print("SecondVC instance initialized.")
22 }
23
24 deinit{
25 print("SecondVC instance deallocated.")
26 }
27}
28
29var secondVC: SecondVC? = SecondVC()
30
31secondVC = nil

Sekarang, jalankan programnya dan kita akan melihat output seperti ini :

Output
1FirstVC instance initialized.
2SecondVC instance initialized.
4SecondVC instance deallocated.
5FirstVC instance deallocated.

Delegation PatternDelegation Pattern

3. Closure

Terakhir, kita bisa temukan Strong Reference Cycle pada Closure, karena Closure merupakan tipe data Reference, dan di dalam Closure kita dapat mengakses property & method yang berada di luar Closure.

Agar lebih jelas, perhatikan kode berikut :

icon
1class Person {
2 var name: String = "Steve"
4 var sayHello: (() -> ())?
5
6 init() {
8 sayHello = {
9 [unowned self] in
10 print("Hello, my name is \(self.name)!")
11 }
13 }
14
15 deinit{
16 print("Person instance deallocated.")
17 }
18}
19
20var person: Person? = Person()
21person?.sayHello!()

Pada contoh kode di atas, class Person dan closure sayHello sama-sama mempunyai Strong reference, jadi ketika kita set nilai dari variabel person menjadi nil, maka method deinit() tidak akan terpanggil.

icon
1person = nil

Strong Reference Cycle - ClosureStrong Reference Cycle - Closure

Solusi :

Untuk memecahkan masalah tersebut, caranya mirip seperti menangani Strong Reference Cycle pada Dependencies dan Delegation Pattern, yaitu mengubah tipe reference menjadi Weak atau Unowned. namun pada Closure agak berbeda, kita akan menggunakan teknik Capture List, yaitu menuliskan property dan method apa saja yang ingin kita pakai di dalam Closure, serta mengubah tipe reference-nya menjadi non-Strong.

Sekarang, ubah closure sayHello menjadi seperti ini :

icon
1init() {
2 sayHello = {
4 [unowned self] in
6 print("Hello, my name is \(self.name)!")
7 }
8}

Lalu jalankan kodenya, dan kita akan melihat output seperti ini :

1Hello, my name is Steve!
3Person instance deallocated.

ClosureClosure

Bagaimana jika Closure kita menerima parameter? kita bisa menempatkan parameter tersebut di antara Capture List dan keyword in, seperti ini :

Capturing List with parameter
icon
1class Person {
2 var name: String = "Steve"
4 var sayHello: ((_ withEmoji: Bool) -> ())?
5
6 init() {
7 sayHello = {
9 [unowned self] (_ withEmoji: Bool) in
10 print("Hello, my name is \(self.name)! \(withEmoji ? "๐Ÿ˜‰" : "")")
12 }
13 }
14
15 deinit{
16 print("Person instance deallocated.")
17 }
18}

Perbedaan antara tipe reference Strong, Weak, dan Unowned

Sampai di sini, kita telah mengetahui perbedaan antara tipe reference Strong, Weak, dan Unowned. namun agar lebih jelas dan mudah diingat, saya akan memberikan poin-poin penting perbedaan antara ketiga tipe reference tersebut :

1. Akan mencegah Class Instance ter-deallocated selama reference masih ada ?

  • Strong : โœ…

  • Weak : โŒ

  • Unowned : โŒ

2. Perlu mendefinisikan tipe datanya menjadi Optional ?

  • Strong : โŒ

  • Weak : โœ… (ARC akan mengubah nilainya menjadi nil jika Class Instance yang di-refer ter-deallocated)

  • Unowned : โŒ

3. Akan menyebabkan crash ketika Class Instance ter-deallocated ?

  • Strong : ??? (masih menjadi misteri, karena memang tidak akan pernah ter-deallocated ๐Ÿคฃ)

  • Weak : โŒ

  • Unowned : โœ…

Kita bisa gunakan tipe reference Weak jika Class Instance yang kita refer berpotensi akan ter-deallocated lebih dulu daripada Class Instance yang me-refer.

Kita bisa gunakan tipe reference Unowned jika suatu reference terjamin akan selalu mengacu pada Class Instance yang kita refer, atau jika kita yakin bahwa Class Instance yang kita refer akan ter-deallocated lebih lama atau sama dengan Class Instance / Closure yang me-refer. (misalnya seperti contoh kasus Strong Reference Cycle pada Closure di atas).

Kalau masih kurang yakin mau pakai Weak atau Unowned, saya sarankan pakai Weak saja. karena lebih aman dan tidak akan menyebabkan Crash seperti tipe reference Unowned apabila digunakan dengan cara yang kurang tepat. ๐Ÿ˜‰


Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! ๐Ÿ˜ ๐Ÿ™

Swift
Memory Management
iOS Development

Written by :
Alfin Syahruddin
Developer ยท Stock Trader ยท Libertarian ยท Freethinker

Always open to new ideas. ๐Ÿ•Š๏ธ

Loading...

Related articles

Articles that you might want to read.

hero image
Error handling pada React JS

Menangani error pada React JS menggunakan react-error-boundary

4 June 2021 ยท 3 Minutes
React
Error Handling
Web Development
hero image
Scope Functions

let, with, run, apply, dan also pada Kotlin.

12 November 2023 ยท 7 Minutes
Android Development
Kotlin
hero image
CI/CD aplikasi iOS dengan Fastlane dan Github Actions

Otomatisasi build, testing, screenshot, dan deployment aplikasi iOS ke Testflight & AppStore.

24 September 2023 ยท 9 Minutes
iOS Development
CI/CD
Fastlane
Github Actions
All rights reserved ยฉ Alfin Syahruddin ยท 2019
RSS Feed