Seperti yang telah kita ketahui, Swift mempunyai 2 tipe data yaitu Reference Type dan Value Type. Reference Type adalah tipe data yang disimpan di heap memory yang dikelola oleh Swift menggunakan ARC (Automatic Reference Counting).
Sedangkan Value Type adalah tipe data yang disimpan di stack memory dan jika dilakukan assignment ke variable lain atau dipassing ke sebuah function maka akan selalu dicopy valuenya.
Sebelumnya saya pernah menulis artikel tentang Automatic Reference Counting (ARC) pada Swift, kalian bisa membacanya di sini.
Problemnya adalah ketika kita mempunyai sebuah variable dengan tipe data Value Type seperti struct yang berukuran sangat besar, maka ketika kita melakukan assignment atau passing ke sebuah function maka valuenya akan selalu dicopy, yang berarti akan memakan waktu dan memori yang cukup besar juga.
Oke, kalau gitu kenapa tidak menggunakan Reference Type saja seperti class? Sebenarnya bisa saja, namun jika kita ingin performa yang lebih baik, cara ini tidak direkomendasikan karena tipe data class akan disimpan di heap memory yang dikelola oleh ARC, yang berarti ada proses retain release, kemudian kita harus memikirkan tentang retain cycle yang bisa menyebabkan memory leaks, dan juga menurut benchmark yang dilakukan oleh Infinum ternyata ARC mempunyai dampak pada performa yang cukup signifikan.
Untuk mengatasi masalah tersebut, Swift 5.9 memperkenalkan konsep Ownership.
Ownership adalah konsep dan aturan yang digunakan untuk mengelola memori di Swift secara efisien dan aman, dimana sebuah variabel hanya bisa dimiliki oleh satu owner saja, dan owner tersebut bertanggung jawab untuk menghapus variabel dari memori ketika variabel tersebut sudah tidak diperlukan lagi.
Konsep Ownership ini mirip dengan konsep Ownership di Rust, dimana setiap variabel hanya bisa dimiliki oleh satu owner saja, dan ketika variabel tersebut dipassing ke variabel lain misalnya, maka ownership dari variabel tersebut akan dipindahkan ke variabel lain tersebut.
Seperti di Rust, kita juga bisa melakukan Borrowing, dimana kita bisa meminjam value dari suatu variabel tanpa harus ada perpindahan ownership.
Perbedaan dengan sistem Ownership di Rust adalah sistem Ownership di Swift ini bersifat opsional, jadi kita bisa gunakan di bagian kode yang membutuhkan performa tinggi saja.
Hal pertama yang perlu kita lakukan untuk menggunakan sistem Ownership adalah dengan menambahkan ~Copyable pada Struct atau Enum kita.
Secara default, Struct & Enum di Swift adalah Copyable, yang berarti ketika kita melakukan assignment atau passing ke sebuah function, maka valuenya akan selalu dicopy. Namun, kita bisa mengubahnya menjadi Non-Copyable dengan cara menambahkan ~Copyable pada Struct atau Enum kita.
Contohnya seperti ini:
1struct Book: ~Copyable {2 var price: String3}45let book = Book(price: 100_000)6let book2 = book7print(book2.price) // ✅ OK8print(book.price) // ❌ Compilation Error: 'book' used after consume
Pada contoh di atas, kita membuat Non-Copyable struct Book dan kita melakukan assignment let book2 = book, ownership dari book akan dipindahkan ke book2, sehingga ketika kita mencoba mengakses book.price maka akan terjadi error pada saat dicompile.
Berikut adalah beberapa limitasi dari ~Copyable untuk saat ini:
Hanya bisa digunakan pada Struct & Enum
Struct & Enum yang menggunakan ~Copyable, properti2-nya juga harus ~Copyable
Belum mendukung Generic
Jika kita tidak ingin memindahkan ownership dari suatu variabel pada saat passing variabel tersebut ke suatu function, maka kita bisa melakukan Borrowing. Borrowing adalah proses meminjam value dari suatu variabel tanpa memindahkan ownership dari variabel tersebut.
Berikut adalah contoh penggunaan Borrowing:
1struct Book: ~Copyable {2 var price: Int34 borrowing func read() {5 print("Reading book...")6 }7}89func describeBook(_ book: borrowing Book) {10 print("Price: \(book.price)")11}
Jika functionnya berada di luar struct maka kita perlu menambahkan keyword borrowing pada parameter functionnya.
Tapi jika functionnya di dalam struct maka kita perlu menambahkan keyword borrowing pada awal deklarasi functionnya dan secara default sebenarnya tipenya adalah borrowing, jadi kita bisa hapus keyword borrowing nya.
1struct Book: ~Copyable {2 var price: Int34 func read() {5 print("Reading book...")6 }7}
Seperti borrowing, inout juga digunakan untuk meminjam value dari suatu variabel, namun inout dapat digunakan jika kita ingin mengubah value dari variabel tersebut.
Berikut adalah contoh penggunaan inout:
1func modifyPrice(_ book: inout Book) {2 book.price = 9993}45modifyPrice(&book)
Pada kode di atas, kita membuat function modifyPrice yang menerima parameter inout Book, dan kita memanggil function tersebut dengan menambahkan & sebelum variabel book.
Jika kita ingin memindahkan ownership dari suatu variabel pada saat passing variabel ke suatu function, maka kita bisa melakukan Consuming. Dan apabila sudah mencapai akhir dari scope function tersebut, maka variabel tersebut akan dihapus dari memori.
Berikut adalah contoh penggunaan Consuming:
1struct Book: ~Copyable {2 var price: Int34 consuming func delete() {5 print("Deleting book from the database...")6 }7}
Pada kode di atas, kita menambahkan keyword consuming pada function delete(), sehingga ketika kita memanggil function delete() 2x maka akan terjadi error pada saat dicompile.
1let book = Book(price: 100_000)2book.delete() // ✅ OK3book.delete() // ❌ Compilation Error: 'book' consumed more than once
Terakhir, ada keyword discard yang digunakan untuk mencegah deinit dipanggil pada saat variabel tersebut dihapus dari memori.
Berikut adalah contoh penggunaan discard:
1struct Book: ~Copyable {2 var price: Int34 consuming func delete() {5 print("Deleting book from the database...")6 discard self7 }89 deinit {10 print("Deleting book from the database...")11 }12}
Pada kode di atas, kita menambahkan keyword discard self pada function delete(), sehingga ketika kita memanggil function delete() maka deinit tidak akan dipanggil.
Infinum telah melakukan benchmark terhadap sistem Ownership di Swift, berikut adalah hasilnya:
Sekarang, kita sudah belajar tentang Ownership & Borrowing di Swift, agar lebih mudah diingat, berikut adalah perbedaan utama antara borrowing, inout, dan consuming:
borrowing: Digunakan untuk meminjam value dari suatu variabel (Read Only).
inout: Digunakan untuk meminjam value dari suatu variabel dan kita bisa mengubah valuenya.
consuming: Digunakan untuk memindahkan ownership dari suatu variabel.
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! 😁 🙏
Referensi:
Always open to new ideas. 🕊️
Articles that you might want to read.
Tutorial memanggil kode Rust di Swift dan Kotlin menggunakan UniFFI.
Otomatisasi build, testing, screenshot, dan deployment aplikasi iOS ke Testflight & AppStore.
Menulis kode yang mudah untuk di-test pada Swift.