Belajar Kontrol Akses Swift dari Kode Nyata yang Bisa Kamu Coba

Belajar Kontrol Akses Swift dari Kode Nyata yang Bisa Kamu Coba
Photo by Árpád Czapp/Unsplash

Ngobrolin soal ngoding itu emang seru, apalagi kalau udah masuk ke level yang bikin kode kita makin rapi dan aman. Salah satu konsep fundamental di Swift yang seringkali diabaikan tapi penting banget adalah Access Control. Mungkin kedengarannya agak ribet, tapi gampangannya ini tuh kayak privasi di media sosialmu, siapa yang boleh lihat postinganmu, siapa yang boleh komen, dan siapa yang cuma bisa nge-stalk doang tanpa bisa ngapa-ngapain. Nah, di Swift, Access Control itu ngatur siapa aja yang boleh pakai atau ngakses bagian-bagian kode yang udah kamu bikin, baik itu properti, fungsi, atau bahkan class dan struct.

Kenapa sih ini penting? Bayangin aja kalau kamu lagi bikin aplikasi gede bareng tim. Kalau semua orang bisa ngutak-ngatik semua bagian kode seenaknya, wah bisa chaos banget. Bugs bermunculan di mana-mana, susah dicari, dan bikin kepala pusing tujuh keliling. Nah, Access Control ini jadi semacam pagar pembatas yang ngelindungin kode internalmu dari jangkauan yang nggak semestinya, sekaligus ngejelasin bagian mana aja yang emang disiapin buat dipakai sama modul lain atau bahkan developer lain. Intinya, ini ngebantu kita bikin kode yang lebih terstruktur, aman, dan gampang di-maintain.

Ada lima level Access Control di Swift yang perlu kamu kenal: private, fileprivate, internal, public, dan open. Masing-masing punya fungsi dan batasannya sendiri. Yuk, kita bedah satu per satu dengan contoh kode nyata biar kamu makin paham dan bisa langsung nyoba di project-mu.

---

1. private: Sangat Eksklusif, Cuma Buat Diri Sendiri

Ini level Access Control paling ketat, bro. Kalau kamu tandain suatu properti atau method dengan private, artinya cuma bisa diakses dari dalam scope tempat dia didefinisikan aja. Maksudnya, kalau kamu bikin variabel private di dalam sebuah class, ya cuma method-method di dalam class itu aja yang bisa ngakses variabel tersebut. Nggak bisa diakses dari instance class lain, apalagi dari file lain. Ini cocok banget buat ngejaga detail implementasi yang cuma butuh diurus internal aja.

Kapan Pakai private?

  • Properti Internal: Kalau ada data di dalam class atau struct yang cuma dibutuhkan buat perhitungan internal dan nggak perlu diakses dari luar.
  • Helper Methods: Fungsi-fungsi kecil yang membantu sebuah method utama bekerja, tapi nggak perlu dipanggil langsung dari luar.

Contoh Kode Nyata:

swift
struct BankAccount {
    let accountNumber: String
    var balance: Double
    private let transactionLimit: Double = 10000.0 // Hanya bisa diakses di dalam BankAccountinit(accountNumber: String, initialBalance: Double) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
    }mutating func deposit(amount: Double) {
        guard amount > 0 else {
            print("Jumlah deposit harus lebih dari nol.")
            return
        }
        balance += amount
        print("Deposit \(amount) berhasil. Saldo baru: \(balance)")
    }mutating func withdraw(amount: Double) {
        guard amount > 0 else {
            print("Jumlah penarikan harus lebih dari nol.")
            return
        }guard balance >= amount else {
            print("Saldo tidak cukup.")
            return
        }// Cek limit transaksi, hanya bisa diakses di sini
        if amount > transactionLimit {
            print("Penarikan melebihi batas transaksi harian \(transactionLimit).")
            return
        }balance -= amount
        print("Penarikan \(amount) berhasil. Saldo baru: \(balance)")
    }// Contoh private helper method
    private func logTransaction(type: String, amount: Double, newBalance: Double) {
        print("[\(type)] Amount: \(amount), New Balance: \(newBalance)")
    }func checkDetails() {
        print("No Rekening: \(accountNumber), Saldo: \(balance)")
        // Kita bisa akses transactionLimit di sini karena masih di dalam scope BankAccount
        print("Batas Transaksi: \(transactionLimit)")
        logTransaction(type: "INFO", amount: 0, newBalance: balance) // Bisa panggil private method
    }
}var myAccount = BankAccount(accountNumber: "123456789", initialBalance: 50000.0)
myAccount.deposit(amount: 15000.0)
myAccount.withdraw(amount: 5000.0)
myAccount.checkDetails()

Di contoh ini, transactionLimit dan logTransaction nggak bisa diakses dari luar BankAccount karena udah ditandai private. Ini menjaga logika internal BankAccount tetap rapi dan nggak diutak-atik dari luar.

---

2. fileprivate: Sedikit Lebih Santai, Tapi Masih Keluarga Dekat

fileprivate itu mirip private, tapi batasannya sedikit lebih longgar. Kalau private cuma bisa diakses di scope yang mendefinisikannya (misalnya di dalam class itu sendiri), fileprivate bisa diakses dari mana saja di dalam file sumber yang sama tempat dia didefinisikan. Jadi, kalau kamu punya dua struct atau class di file .swift yang sama, dan salah satu punya properti fileprivate, struct atau class lainnya bisa ngakses itu.

Kapan Pakai fileprivate?

  • Multi-Type di Satu File: Ketika kamu punya beberapa tipe (struct, class, enum) yang saling berhubungan erat dan didefinisikan dalam satu file, dan mereka perlu berbagi beberapa detail implementasi yang nggak perlu diekspos keluar file.

Contoh Kode Nyata:

Bayangin kamu lagi bikin sistem notifikasi. Ada NotificationManager dan NotificationFormatter di file yang sama.

swift
// File: NotificationService.swiftstruct NotificationFormatter {
    fileprivate func format(message: String) -> String { // Hanya bisa diakses di file ini
        let timestamp = Date()
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "HH:mm"
        return "[\(dateFormatter.string(from: timestamp))] \(message)"
    }
}class NotificationManager {
    private let formatter = NotificationFormatter() // Instance formatterfunc sendNotification(text: String) {
        // NotificationManager bisa panggil format() karena NotificationFormatter.format()
        // adalah fileprivate dan berada di file yang sama
        let formattedMessage = formatter.format(message: text)
        print("Mengirim notifikasi: \(formattedMessage)")
        // Di sini bisa ada logika kirim notifikasi ke sistem
    }// Contoh lain, kalau ada helper struct di file yang sama
    struct InternalLogger {
        fileprivate func logActivity(description: String) { // accessible by other types in this file
            print("INTERNAL LOG: \(description)")
        }
    }func performBackgroundTask() {
        InternalLogger().logActivity(description: "Background task started.")
    }
}let manager = NotificationManager()
manager.sendNotification(text: "Pesanan kamu sudah tiba!")
manager.performBackgroundTask()

Di sini, format method di NotificationFormatter bisa dipanggil oleh NotificationManager karena mereka berada dalam file yang sama. Tapi kalau NotificationFormatter ini di-instantiate di file lain, method format nggak akan bisa diakses. Begitu juga InternalLogger.logActivity yang bisa dipanggil dari NotificationManager.

---

3. internal: Standar Operasional Kita Semua

Kalau kamu nggak nulis Access Control modifier sama sekali, default-nya di Swift itu internal. Artinya, entitas yang ditandai internal (atau nggak ditandai sama sekali) bisa diakses dari mana saja di dalam modul yang sama. Modul ini bisa berupa target aplikasi utama kamu, sebuah framework, atau sebuah library.

Kapan Pakai internal?

  • Sebagian Besar Kode Aplikasi: Sebagian besar kode yang kamu tulis dalam satu aplikasi Swift akan menggunakan internal Access Control. Kamu pengen semua bagian aplikasi kamu bisa saling berinteraksi, tapi nggak perlu diekspos ke luar aplikasi itu sendiri (misalnya kalau aplikasi kamu pakai framework pihak ketiga).

Contoh Kode Nyata:

Bayangin kamu punya aplikasi to-do list.

swift
// File: TodoItem.swift
struct TodoItem { // Defaultnya internal
    var title: String
    var isCompleted: Boolinit(title: String, isCompleted: Bool = false) {
        self.title = title
        self.isCompleted = isCompleted
    }func getStatus() -> String {
        return isCompleted ? "Selesai" : "Belum Selesai"
    }
}// File: TodoManager.swift
class TodoManager { // Defaultnya internal
    var items: [TodoItem] = [] // TodoItem bisa diakses karena internal dan di modul yang samafunc addTodo(title: String) {
        let newItem = TodoItem(title: title)
        items.append(newItem)
        print("Ditambahkan: '\(title)'")
    }func markAsCompleted(index: Int) {
        guard index >= 0 && index < items.count else {
            print("Indeks tidak valid.")
            return
        }
        items[index].isCompleted = true
        print("Item '\(items[index].title)' ditandai selesai.")
    }func listAllTodos() {
        print("\nDaftar Tugas:")
        for (index, item) in items.enumerated() {
            print("\(index + 1). \(item.title) - \(item.getStatus())")
        }
    }
}// File: ViewController.swift (Ini semua berada dalam modul aplikasi yang sama)
let manager = TodoManager() // Bisa diakses
manager.addTodo(title: "Belajar Swift Access Control")
manager.addTodo(title: "Bikin artikel blog")
manager.addTodo(title: "Revisi kode project X")manager.markAsCompleted(index: 0)manager.listAllTodos()

Di contoh ini, baik TodoItem maupun TodoManager secara default adalah internal. Ini memungkinkan mereka untuk diakses dan digunakan di mana saja dalam aplikasi kamu (misalnya dari ViewController atau file lain di target aplikasi yang sama).

---

4. public: Terbuka Lebar untuk Dunia Luar (Framework/Library)

Ini level Access Control yang paling sering kamu lihat kalau lagi pakai framework atau library dari pihak ketiga (misalnya SwiftUI, UIKit, atau framework bikinan developer lain). Ketika kamu tandai sesuatu dengan public, itu berarti bisa diakses dari modul mana pun yang mengimpor modul yang mendefinisikannya. Jadi, kalau kamu bikin sebuah framework, dan kamu pengen aplikasi lain bisa pakai class atau method di dalamnya, kamu harus tandai sebagai public.

Kapan Pakai public?

  • API Framework/Library: Ketika kamu membangun sebuah framework atau library yang akan digunakan oleh aplikasi atau modul lain. Semua interface (properti, method, class, struct) yang kamu ingin expose ke pengguna framework harus public.

Contoh Kode Nyata:

Bayangin kamu bikin sebuah framework sederhana untuk validasi input.

swift
// --- Ini adalah bagian dari Framework "ValidationKit" ---// File: Validator.swift (dalam target framework ValidationKit)
public protocol Validator { // Protocol juga bisa public
    func isValid(_ text: String) -> Bool
    func errorMessage() -> String
}public struct EmailValidator: Validator { // Struct juga bisa public
    public init() {} // Inits juga harus public kalau mau dipanggil dari luarpublic func isValid(_ text: String) -> Bool {
        // Implementasi validasi email sederhana
        return text.contains("@") && text.contains(".")
    }public func errorMessage() -> String {
        return "Format email tidak valid."
    }
}public struct PasswordValidator: Validator {
    public init() {}public func isValid(_ text: String) -> Bool {
        return text.count >= 8 && text.rangeOfCharacter(from: .letters) != nil && text.rangeOfCharacter(from: .decimalDigits) != nil
    }public func errorMessage() -> String {
        return "Password minimal 8 karakter, mengandung huruf dan angka."
    }
}// --- Ini adalah bagian dari Aplikasi Utama yang Mengimpor ValidationKit ---// File: MainApp.swift (dalam target aplikasi utama)
import ValidationKit // Kita import framework yang sudah publicfunc validateUserInput(text: String, validator: Validator) {
    if validator.isValid(text) {
        print("'\(text)' valid.")
    } else {
        print("'\(text)' tidak valid: \(validator.errorMessage())")
    }
}let emailVal = EmailValidator() // Bisa diinstantiate karena public
validateUserInput(text: "user@example.com", validator: emailVal)
validateUserInput(text: "invalid-email", validator: emailVal)let passwordVal = PasswordValidator() // Bisa diinstantiate karena public
validateUserInput(text: "strongPass123", validator: passwordVal)
validateUserInput(text: "weak", validator: passwordVal)

Dalam skenario ini, ValidationKit adalah sebuah framework, dan EmailValidator, PasswordValidator, serta Validator protocol adalah bagian dari public API-nya. Aplikasi utama bisa mengimpor ValidationKit dan langsung menggunakan EmailValidator dan PasswordValidator karena mereka dideklarasikan sebagai public.

---

5. open: Pintu Terbuka Lebar untuk Subclassing dan Overriding

Ini adalah Access Control yang paling liberal, dan hanya berlaku untuk class dan member (method, property) dari class tersebut. open itu mirip public, artinya bisa diakses dari modul mana pun. Tapi ada satu perbedaan krusial: public class: Bisa diakses dari modul lain, tapi tidak bisa di-subclass di luar modulnya sendiri. Member public dari sebuah class juga tidak bisa di-override* di luar modulnya. open class: Bisa diakses dari modul lain, dan bisa di-subclass di luar modulnya sendiri. Member open dari sebuah class juga bisa di-override* di luar modulnya.

Kapan Pakai open?

  • Framework yang Dirancang untuk Kustomisasi: Ketika kamu bikin framework dan sengaja pengen developer lain bisa nge-subclass class kamu atau nge-override method-method tertentu buat kustomisasi. Ini butuh komitmen jangka panjang karena perubahan pada open class atau method bisa memengaruhi kode orang lain yang udah nge-subclass.

Contoh Kode Nyata:

Misalnya kamu bikin framework UI Custom.

swift
// --- Ini adalah bagian dari Framework "CustomUIFramework" ---// File: BaseButton.swift (dalam target framework CustomUIFramework)
open class BaseButton { // Bisa di-subclass di modul lain
    public let title: String
    private var tapCount: Int = 0 // Ini tetap privatepublic init(title: String) {
        self.title = title
    }open func handleTap() { // Bisa di-override di modul lain
        tapCount += 1
        print("BaseButton '\(title)' tapped! Total taps: \(tapCount)")
    }public func displayInfo() { // Public, tapi tidak bisa di-override di luar modul ini
        print("Button: \(title)")
    }
}// File: CustomViewController.swift (dalam target framework CustomUIFramework)
open class CustomViewController { // Bisa di-subclass di modul lain
    public var title: Stringpublic init(title: String) {
        self.title = title
    }open func viewDidLoad() { // Bisa di-override
        print("CustomViewController \(title) did load.")
    }
}// --- Ini adalah bagian dari Aplikasi Utama yang Mengimpor CustomUIFramework ---// File: MainApp.swift (dalam target aplikasi utama)
import CustomUIFramework// 1. Subclassing open class
class MyCustomButton: BaseButton { // Ini legal karena BaseButton adalah open
    private var customTapMessage: Stringinit(title: String, customMessage: String) {
        self.customTapMessage = customMessage
        super.init(title: title)
    }override func handleTap() { // Override method open
        super.handleTap() // Panggil implementasi superclass
        print("Oh iya, ini pesan kustom: \(customTapMessage)")
    }// Kalau kamu coba override displayInfo(), ini akan error:
    // override func displayInfo() { ... } // Error: Method does not override any method from its superclass
}let myButton = MyCustomButton(title: "Tekan Saya", customMessage: "Mantap!")
myButton.handleTap() // Memanggil override handleTap()// 2. Menggunakan open class secara langsung
let baseBtn = BaseButton(title: "Tombol Default")
baseBtn.handleTap()// 3. Subclassing CustomViewController
class MyOwnViewController: CustomViewController {
    override func viewDidLoad() { // Override method open
        super.viewDidLoad()
        print("MyOwnViewController \(title) is loading with some extra logic!")
    }
}

Di sini, BaseButton dan CustomViewController dideklarasikan sebagai open, dan method handleTap() serta viewDidLoad() juga open. Ini memungkinkan MyCustomButton dan MyOwnViewController di aplikasi utama untuk nge-subclass dan nge-override fungsionalitasnya. Kalau BaseButton cuma public, kamu nggak akan bisa bikin MyCustomButton yang nge-subclass BaseButton di modul lain.

---

Tips dan Praktik Terbaik dalam Menggunakan Access Control

Oke, udah paham kan bedanya? Sekarang, biar kode kamu makin jago, ada beberapa tips dan praktik terbaik nih:

  1. Start with internal (atau private): Jangan langsung buru-buru pakai public atau open. Kalau nggak yakin, mulai aja dengan internal (yang default) atau private kalau kamu pengen bener-bener dijaga. Kamu selalu bisa meningkatkan level aksesnya nanti kalau memang dibutuhkan. Prinsipnya "least privilege", berikan akses seminimal mungkin.
  2. Pikirkan API Surface Area: Kalau kamu bikin framework, bayangin apa aja yang harus dilihat dan dipakai oleh developer lain. Bagian-bagian itulah yang harus public atau open. Sisanya, tetap internal atau lebih ketat lagi. Ini bikin API kamu bersih, gampang dipahami, dan nggak terlalu banyak "sampah" internal yang bikin bingung.
  3. Konsistensi Itu Kunci: Usahakan konsisten dalam penggunaan Access Control di project kamu. Kalau tim kamu sepakat pakai private untuk helper method, ya semua helper method harus private.
  4. public vs open untuk Class dan Member Class: Ini krusial banget buat developer framework.

Pakai public kalau kamu pengen sebuah class bisa dipakai tapi nggak bisa di-subclass* di luar modulmu. Ini ngasih kamu fleksibilitas lebih buat refactor internal class itu tanpa khawatir ngerusak kode orang lain. * Pakai open kalau kamu emang sengaja ngasih kesempatan orang lain buat nge-subclass class kamu atau nge-override method tertentu. Ini berarti kamu harus hati-hati banget kalau ada perubahan, karena bisa jadi breaking change buat yang udah nge-subclass.

  1. Access Control pada Protokol dan Ekstensi:

* Protokol: Access level sebuah protokol berlaku untuk semua persyaratan yang didefinisikan dalam protokol itu. Jadi kalau sebuah protokol public, semua member yang conform ke protokol itu minimal harus public juga. * Ekstensi: Kalau kamu bikin ekstensi, access level-nya biasanya ngikutin tipe aslinya. Tapi kamu bisa nambahin Access Control modifier ke individual member di ekstensi tersebut. Kalau sebuah ekstensi menambah fungsionalitas ke tipe yang didefinisikan di modul lain, member ekstensi nggak bisa punya access level yang lebih ketat dari member tipe aslinya.

  1. Tuples dan Fungsi: Access level sebuah tuple type atau function type ditentukan oleh access level yang paling ketat dari semua tipe yang dipakai di dalamnya. Misalnya, sebuah fungsi yang mengembalikan private struct akan secara otomatis punya access level private juga, bahkan jika fungsi itu sendiri dideklarasikan sebagai public.

Hindari Jebakan-Jebakan Ini

  • Terlalu Banyak public: Ini kesalahan umum. Kalau semua dibikin public, kode internalmu jadi terekspos semua. Sulit di-maintain, rawan bugs, dan bikin dependensi yang nggak perlu.
  • Bingung private vs fileprivate: Ingat, private itu sangat lokal (di dalam scope definisi), sementara fileprivate itu lingkupnya satu file. Pilih yang sesuai kebutuhan. Kalau cuma butuh di dalam class itu sendiri, pakai private. Kalau butuh berbagi antar beberapa tipe di satu file, baru pakai fileprivate.
  • Mengabaikan open: Kalau kamu bikin framework dan ingin developer lain bebas kustomisasi, jangan cuma pakai public. Pastikan kamu pakai open untuk class dan method yang memang dimaksudkan untuk di-subclass atau di-override.

---

Penutup

Memahami dan menerapkan Access Control dengan benar di Swift itu krusial banget buat bikin kode yang kokoh, terstruktur, dan mudah di-maintain. Ini bukan cuma soal ngatur siapa yang boleh lihat, tapi juga soal mendesain arsitektur aplikasi atau framework yang bersih dan efisien. Dengan latihan dan eksperimen, kamu bakal terbiasa dan bisa memutuskan kapan harus pakai private, fileprivate, internal, public, atau open dengan tepat. Jadi, nggak ada lagi alasan buat kode yang berantakan dan rawan bugs, ya! Yuk, langsung dicoba di project-mu dan rasakan bedanya! Happy ngoding!

Read more