This episode covers OOP in Swift: classes, inheritance, and method overriding, access control ranging from public to private, protocols with protocol extensions as the foundation of protocol-oriented programming, as well as generics and type constraints for safe and reusable code.

Swift is a multi-paradigm language, and its two most important pillars are object-oriented programming and protocol-oriented programming. Episode 5 covers both: classes with inheritance, access rules, protocols as behavioral contracts, and generics for writing abstract, type-safe code.
Many beginner developers think of protocols as merely a complement to OOP. In reality, Swift's official philosophy places protocols at the center — Swift even adds features such as protocol extensions and protocol composition that go beyond what interfaces offer in other languages.
Classes in Swift support inheritance: a class can inherit properties, methods, and initializers from a parent. The parent class is called a superclass, and the child class is called a subclass:
class Kendaraan {
var roda = 4
func deskripsi() -> String {
return "Kendaraan dengan \(roda) roda"
}
}
class Mobil: Kendaraan {
var bahanBakar = "bensin"
}
let mobil = Mobil()
print(mobil.deskripsi())
print(mobil.bahanBakar)class Mobil: Kendaraan inherits every member of Kendaraan and adds its own property. Inheritance allows code to be shared, but don't overdo it — hierarchies that are too deep become fragile to change.
A subclass can replace a parent method's implementation with the override keyword:
class MobilListrik: Kendaraan {
var kapasitasBaterai = 60
override func deskripsi() -> String {
return "Mobil listrik dengan baterai \(kapasitasBaterai) kWh"
}
}
let tesla = MobilListrik()
print(tesla.deskripsi())override func deskripsi() replaces the parent's behavior. Swift requires the override keyword to prevent accidental overriding — without it, compilation fails.
Swift provides five access levels, from most open to most restricted:
public struct AkunPengguna {
public private(set) var saldo: Double = 0
private var pin: String = "0000"
public mutating func tambahSaldo(_ jumlah: Double) {
saldo += jumlah
}
}public private(set) var saldo makes the saldo property publicly readable but only mutable within the type. Disciplined access control protects internal invariants — this is where encapsulation really comes to life.
A protocol describes the properties and methods that adopting types must have, without storing any implementation:
protocol MenampilkanRingkas {
var judulRingkas: String { get }
func ringkasan() -> String
}
struct Artikel: MenampilkanRingkas {
var judulRingkas: String
var isi: String
func ringkasan() -> String {
return "\(judulRingkas): \(isi.prefix(40))"
}
}struct Artikel: MenampilkanRingkas declares a commitment to fulfill the protocol's contract. The compiler checks conformance — forgetting to implement a method means a compile error, not a runtime bug.
A protocol extension provides a default implementation so that every type adopting the protocol automatically gets the behavior:
extension MenampilkanRingkas {
func lengkap() -> String {
return judulRingkas + " - " + ringkasan()
}
}
let artikel = Artikel(judulRingkas: "Swift", isi: "Pemrograman modern")
print(artikel.lengkap())extension MenampilkanRingkas adds the lengkap() method for all types adopting the protocol. This is the essence of protocol-oriented programming: behavior is composed through protocols and default implementations, rather than inherited through rigid class hierarchies.
Generics let you write code that works across many types without sacrificing type safety:
func tukar<T>(_ a: inout T, _ b: inout T) {
let sementara = a
a = b
b = sementara
}
var x = 1
var y = 2
tukar(&x, &y)
print(x, y)func tukar<T>(_ a: inout T) works for any type — Int, String, or a custom type. The inout marker means the parameters are modified in place. Generics prevent you from writing duplicate code for every type.
Sometimes you need to restrict which types are allowed, for example types that conform to a specific protocol:
func gabungkan<T: StringProtocol>(_ a: T, _ b: T) -> String {
return "\(a) + \(b)"
}
print(gabungkan("Swift", "iOS"))T: StringProtocol restricts T to types adopting StringProtocol. Constraints let the compiler call that protocol's members on generic parameters — the combination of generics and protocols is the most powerful pair in Swift.
Info
The combination of generics, protocols, and protocol extensions is the foundation of many modern Swift APIs such as Collection, View in SwiftUI, and Publisher in Combine. Watch for this pattern when reading third-party code.
Key takeaways:
override keyword.In the next episode, episode 6, we'll cover error handling and debugging — handling errors with throws, try, catch, and defer, creating custom error types, debugging in Xcode with breakpoints and LLDB, and assertions and unit tests to validate behavior. Time to make your code resilient!