Section 21/221 menit
15. Real Use Cases
15. Real Use Cases
Use Case 1: Menyelidiki dan Memperbaiki Slow File Compilation
Tim menemukan bahwa file CheckoutViewModel.swift membutuhkan 8 detik untuk dikompilasi — jauh di atas normal.
swift
// CheckoutViewModel.swift — SEBELUM optimasi
// File ini membutuhkan 8.2 detik untuk dikompilasi
import Foundation
import Combine
class CheckoutViewModel: ObservableObject {
@Published var items: [CartItem] = []
@Published var total: Decimal = 0
@Published var discount: Decimal = 0
@Published var tax: Decimal = 0
@Published var finalTotal: Decimal = 0
// ❌ Expression sangat kompleks — inference hell
var summary: String {
"Total: \(NumberFormatter.localizedString(from: items.map { $0.price * Decimal($0.quantity) }.reduce(0, +) * (1 - discount) * (1 + tax) as NSDecimalNumber, number: .currency)) (\(items.count) items, \(String(format: "%.0f%%", Double(discount) * 100)) off)"
}
// ❌ Generic function overused
func calculate<T: Numeric & Comparable>(_ values: [T], using operation: (T, T) -> T) -> T {
values.reduce(values[0], operation)
}
// ❌ Nested ternary dalam computed property
var checkoutState: String {
items.isEmpty ? "empty" : total < 0 ? "invalid" : discount > 1 ? "over-discounted" : tax < 0 ? "invalid-tax" : "ready"
}
}
swift
// CheckoutViewModel.swift — SETELAH optimasi
// File ini sekarang membutuhkan 0.4 detik untuk dikompilasi
import Foundation
import Combine
final class CheckoutViewModel: ObservableObject {
@Published var items: [CartItem] = []
@Published var total: Decimal = 0
@Published var discount: Decimal = 0
@Published var tax: Decimal = 0
@Published var finalTotal: Decimal = 0
// ✓ Tipe eksplisit, expression dipecah
var summary: String {
let subtotal: Decimal = items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
let discounted: Decimal = subtotal * (1 - discount)
let withTax: Decimal = discounted * (1 + tax)
let formatted: String = NumberFormatter.localizedString(from: withTax as NSDecimalNumber, number: .currency)
let discountPct: Int = Int(Double(truncating: discount as NSDecimalNumber) * 100)
return "\(formatted) (\(items.count) items, \(discountPct)% off)"
}
// ✓ Fungsi konkret, bukan generic berlebihan
private func sumPrices(_ items: [CartItem]) -> Decimal {
items.reduce(0) { $0 + $1.price * Decimal($1.quantity) }
}
// ✓ Guard + early return menggantikan nested ternary
var checkoutState: CheckoutState {
guard !items.isEmpty else { return .empty }
guard total >= 0 else { return .invalid("Total negatif") }
guard discount <= 1 else { return .invalid("Diskon melebihi 100%") }
guard tax >= 0 else { return .invalid("Pajak negatif") }
return .ready
}
}
enum CheckoutState {
case empty
case invalid(String)
case ready
}
Langkah investigasi yang digunakan:
swift
# 1. Aktifkan warning untuk slow compilation
# Di Build Settings → OTHER_SWIFT_FLAGS (Debug):
# -Xfrontend -warn-long-function-bodies=200
# -Xfrontend -warn-long-expression-type-checking=200
# 2. Build dan cari warning
xcodebuild build -scheme MyApp 2>&1 | grep "expression took"
# Output: CheckoutViewModel.swift:18: warning: expression took 3847ms to type-check
# 3. Identifikasi lokasi exact → perbaiki
Use Case 2: Setup CI Pipeline dengan Full Caching
Tim pindah dari CircleCI (no caching) ke GitHub Actions dengan caching lengkap. Build time CI turun dari 22 menit ke 8 menit.
swift
# .github/workflows/pr-check.yml
# PR Check: fast feedback, hanya unit test
name: PR Check
on:
pull_request:
branches: [main, develop]
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true # Batalkan run lama jika PR diupdate
jobs:
build-and-test:
runs-on: macos-15
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true # Otomatis cache gems via bundler
- name: Cache CocoaPods
uses: actions/cache@v4
id: pods-cache
with:
path: Pods
key: pods-v2-${{ runner.os }}-${{ hashFiles('Podfile.lock') }}
restore-keys: pods-v2-${{ runner.os }}-
- name: Cache SPM
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData/**/SourcePackages
key: spm-v2-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}
restore-keys: spm-v2-${{ runner.os }}-
- name: Install Pods
if: steps.pods-cache.outputs.cache-hit != 'true'
run: bundle exec pod install
- name: Build (no tests)
run: |
xcodebuild build \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-configuration Debug \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify --is-ci
- name: Unit Tests
run: |
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-configuration Debug \
-only-testing:MyAppTests \
-resultBundlePath TestResults.xcresult \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify --is-ci
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ github.run_id }}
path: TestResults.xcresult
retention-days: 7
Use Case 3: Migrasi ke Local SPM Package untuk Mempercepat Incremental Build
Team yang punya monolithic target dengan 400 file memecahnya menjadi local packages. Incremental build turun dari 45 detik ke 8 detik untuk perubahan di satu modul.
swift
// Package.swift di root project
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "MyAppModules",
platforms: [.iOS(.v16)],
products: [
.library(name: "CoreDomain", targets: ["CoreDomain"]),
.library(name: "NetworkKit", targets: ["NetworkKit"]),
.library(name: "UIComponents", targets: ["UIComponents"]),
.library(name: "HomeFeature", targets: ["HomeFeature"]),
],
dependencies: [
// External dependencies via SPM
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.8.0"),
],
targets: [
// Layer 1: Domain (tidak ada dependency internal)
.target(
name: "CoreDomain",
dependencies: [],
path: "Sources/CoreDomain"
),
// Layer 2: Network (depend on CoreDomain)
.target(
name: "NetworkKit",
dependencies: [
"CoreDomain",
.product(name: "Alamofire", package: "Alamofire"),
],
path: "Sources/NetworkKit"
),
// Layer 2: UI (depend on CoreDomain)
.target(
name: "UIComponents",
dependencies: ["CoreDomain"],
path: "Sources/UIComponents",
resources: [
.process("Resources"),
]
),
// Layer 3: Feature (depend on semua layer 2)
.target(
name: "HomeFeature",
dependencies: ["NetworkKit", "UIComponents", "CoreDomain"],
path: "Sources/HomeFeature"
),
// Tests
.testTarget(
name: "CoreDomainTests",
dependencies: ["CoreDomain"],
path: "Tests/CoreDomainTests"
),
.testTarget(
name: "NetworkKitTests",
dependencies: ["NetworkKit"],
path: "Tests/NetworkKitTests"
),
]
)
swift
// Sources/CoreDomain/User.swift
// Domain model — tidak bergantung pada framework apapun
public struct User: Identifiable, Sendable {
public let id: UUID
public var name: String
public var email: String
public var tier: Tier
public enum Tier: String, Sendable {
case free, premium, enterprise
}
public init(id: UUID = .init(), name: String, email: String, tier: Tier = .free) {
self.id = id
self.name = name
self.email = email
self.tier = tier
}
}
swift
// Sources/NetworkKit/UserRepository.swift
// Network layer — menggunakan CoreDomain types
import Foundation
import CoreDomain
import Alamofire
public protocol UserRepositoryProtocol: Sendable {
func fetchUser(id: UUID) async throws -> User
func updateUser(_ user: User) async throws -> User
}
public final class UserRepository: UserRepositoryProtocol {
private let session: Session
public init(session: Session = .default) {
self.session = session
}
public func fetchUser(id: UUID) async throws -> User {
let response: UserResponse = try await session
.request("https://api.example.com/users/\(id)")
.validate()
.serializingDecodable(UserResponse.self)
.value
return response.toDomain()
}
public func updateUser(_ user: User) async throws -> User {
let body = UpdateUserRequest(user: user)
let response: UserResponse = try await session
.request(
"https://api.example.com/users/\(user.id)",
method: .put,
parameters: body,
encoder: JSONParameterEncoder.default
)
.validate()
.serializingDecodable(UserResponse.self)
.value
return response.toDomain()
}
}
Cara menambahkan local package ke Xcode project:
swift
1. File → Add Package Dependencies
2. Pilih "Add Local..."
3. Navigasi ke folder yang berisi Package.swift
4. Xcode otomatis menambahkan sebagai local dependency
5. Di target → General → Frameworks, Libraries → tambahkan modul yang diperlukan