Section 8/91 menit
8. Real Use Cases
8. Real Use Cases
Use Case 1: Database Transaction Guard
swift
import Foundation
enum TransactionError: Error {
case connectionFailed
case constraintViolation(String)
case deadlock
case rollbackFailed
}
// Transaction yang PASTI di-commit atau di-rollback — tidak ada yang lolos
struct DatabaseTransaction: ~Copyable {
private let id: UUID
private let connection: DatabaseConnection
private var state: State = .active
enum State { case active, committed, rolledBack }
init(connection: DatabaseConnection) throws(TransactionError) {
self.id = UUID()
self.connection = connection
try connection.beginTransaction(id: id)
}
// consuming: setelah commit, transaction tidak bisa dipakai
consuming func commit() throws(TransactionError) {
guard state == .active else { return }
state = .committed
try connection.commitTransaction(id: id)
}
consuming func rollback() {
guard state == .active else { return }
state = .rolledBack
try? connection.rollbackTransaction(id: id)
}
// borrowing: execute query tanpa mengambil ownership transaction
borrowing func execute(_ sql: String) throws(TransactionError) {
guard state == .active else {
throw TransactionError.connectionFailed
}
try connection.execute(sql, in: id)
}
deinit {
// Safety net: jika keluar scope tanpa commit/rollback
if state == .active {
try? connection.rollbackTransaction(id: id)
print("⚠️ Transaction \(id) di-rollback otomatis karena tidak di-commit")
}
}
}
// Helper untuk RAII-style transaction management
func withTransaction<Result>(
on connection: DatabaseConnection,
body: (borrowing DatabaseTransaction) throws -> Result
) throws -> Result {
var tx = try DatabaseTransaction(connection: connection)
do {
let result = try body(tx)
try (consume tx).commit()
return result
} catch {
(consume tx).rollback()
throw error
}
}
// Penggunaan — transaction PASTI di-commit atau di-rollback
func transferFunds(from sourceID: String, to destID: String, amount: Double) async throws {
let connection = try await DatabaseConnection.connect()
try withTransaction(on: connection) { tx in
try tx.execute("UPDATE accounts SET balance = balance - \(amount) WHERE id = '\(sourceID)'")
try tx.execute("UPDATE accounts SET balance = balance + \(amount) WHERE id = '\(destID)'")
try tx.execute("INSERT INTO transfers VALUES ('\(sourceID)', '\(destID)', \(amount))")
// commit terjadi otomatis saat withTransaction selesai
}
// Jika ada exception: rollback otomatis via deinit
}
Use Case 2: Mutex Lock Guard (RAII Pattern)
swift
// RAII: Resource Acquisition Is Initialization
// Lock diperoleh saat init, dilepas saat deinit — tidak bisa bocor
struct MutexGuard<Value>: ~Copyable {
private let mutex: NSLock
private let valuePtr: UnsafeMutablePointer<Value>
fileprivate init(mutex: NSLock, valuePtr: UnsafeMutablePointer<Value>) {
self.mutex = mutex
self.valuePtr = valuePtr
mutex.lock() // acquire saat init
}
// Akses nilai yang dilindungi
var value: Value {
get { valuePtr.pointee }
set { valuePtr.pointee = newValue }
}
deinit {
mutex.unlock() // release PASTI terjadi — tidak ada lock leak
}
}
final class Protected<Value> {
private let mutex = NSLock()
private var _value: Value
init(_ value: Value) {
self._value = value
}
func withLock<Result>(_ body: (inout Value) throws -> Result) rethrows -> Result {
mutex.lock()
defer { mutex.unlock() }
return try body(&_value)
}
// Menggunakan MutexGuard untuk ownership yang jelas
func lock() -> MutexGuard<Value> {
MutexGuard(mutex: mutex, valuePtr: &_value)
}
}
// Penggunaan:
let counter = Protected(0)
// Cara 1: withLock closure
counter.withLock { value in
value += 1
}
// Cara 2: MutexGuard — lock hidup selama guard dalam scope
func incrementAndRead() -> Int {
var guard_ = counter.lock() // lock acquired
guard_.value += 1
let result = guard_.value
return result
// guard_ keluar scope → lock released OTOMATIS
}
Use Case 3: Cryptographic Key Management
swift
import CryptoKit
// Private key yang harus di-zero saat tidak dibutuhkan
struct SecurePrivateKey: ~Copyable {
// P256.Signing.PrivateKey sudah dikelola CryptoKit
// Tapi kita bisa wrap untuk kontrol ownership yang lebih ketat
private var key: P256.Signing.PrivateKey
private let keyID: String
init() {
self.key = P256.Signing.PrivateKey()
self.keyID = UUID().uuidString
logKeyCreation(keyID)
}
// borrowing: sign tanpa memberikan akses ke raw key material
borrowing func sign(_ data: Data) throws -> P256.Signing.ECDSASignature {
return try key.signature(for: data)
}
// Public key aman untuk di-copy dan disebarkan
borrowing func publicKey() -> P256.Signing.PublicKey {
return key.publicKey
}
// Export — consuming karena setelah export key dianggap "habis"
consuming func exportEncrypted(with wrappingKey: SymmetricKey) -> Data {
// Encrypt dan return
// Key material akan di-clear oleh deinit
return Data()
}
deinit {
// Key material di-clear dari memori
// P256.PrivateKey sudah melakukan ini, tapi log kita tambahkan
logKeyDestruction(keyID)
// Dalam implementasi nyata: zero-out semua sensitive bytes
}
private func logKeyCreation(_ id: String) { print("Key created: \(id)") }
private func logKeyDestruction(_ id: String) { print("Key destroyed: \(id)") }
}
// Penggunaan:
func signDocument(_ document: Data) async throws -> SignedDocument {
// Key hanya ada dalam scope ini — tidak bisa bocor ke luar
let key = SecurePrivateKey()
let signature = try key.sign(document)
let publicKey = key.publicKey()
// let exportedKey = key // ❌ ERROR: tidak bisa copy key
return SignedDocument(
data: document,
signature: signature,
signerPublicKey: publicKey
)
// key di-destroy di sini — zero-out dari memori
}
Use Case 4: Connection Pool dengan Guaranteed Return
swift
// Connection yang PASTI dikembalikan ke pool setelah dipakai
final class ConnectionPool {
private var available: [Connection] = []
private let mutex = NSLock()
// Mengembalikan ticket — bukan connection langsung
func acquire() -> PooledConnection? {
mutex.lock()
defer { mutex.unlock() }
guard let connection = available.popLast() else { return nil }
return PooledConnection(connection: connection, pool: self)
}
fileprivate func returnConnection(_ connection: Connection) {
mutex.lock()
defer { mutex.unlock() }
available.append(connection)
}
}
// Noncopyable: connection tidak bisa di-share atau di-copy
struct PooledConnection: ~Copyable {
private let connection: Connection
private weak var pool: ConnectionPool?
fileprivate init(connection: Connection, pool: ConnectionPool) {
self.connection = connection
self.pool = pool
}
borrowing func query(_ sql: String) throws -> [Row] {
try connection.execute(sql)
}
deinit {
// PASTI dikembalikan ke pool — tidak ada connection leak
pool?.returnConnection(connection)
}
}
// Penggunaan:
func handleRequest(pool: ConnectionPool) async throws -> Response {
guard let conn = pool.acquire() else {
throw ServiceError.noConnectionAvailable
}
// conn tidak bisa di-copy, tidak bisa keluar scope tanpa dikembalikan ke pool
let rows = try conn.query("SELECT * FROM users LIMIT 10")
let response = Response(data: rows)
return response
// conn dikembalikan ke pool OTOMATIS di sini — tidak perlu manual return
}