Section 8/101 menit

8. Real Use Cases

8. Real Use Cases

Use Case 1: Library yang Support Swift 5 dan Swift 6

swift
// Package.swift untuk library publik
// swift-tools-version: 5.10

let package = Package(
    name: "MyNetworkingLibrary",
    platforms: [.iOS(.v15), .macOS(.v12)],
    products: [
        .library(name: "MyNetworking", targets: ["MyNetworking"])
    ],
    targets: [
        .target(
            name: "MyNetworking",
            swiftSettings: [
                // Support Swift 5 dan 6 dengan fitur bertahap
                .swiftLanguageVersion(.v5),
                .enableUpcomingFeature("StrictConcurrency"),
                .enableUpcomingFeature("ExistentialAny"),
            ]
        ),
        .testTarget(
            name: "MyNetworkingTests",
            dependencies: ["MyNetworking"],
            swiftSettings: [
                // Test pakai Swift 6 penuh untuk tangkap issue lebih awal
                .swiftLanguageVersion(.v6)
            ]
        )
    ]
)
swift
// Sources/MyNetworking/NetworkClient.swift

import Foundation

// Sendable: kompatibel Swift 5 dan 6
public struct RequestConfig {
    public let url: URL
    public let method: String
    public let headers: [String: String]
    public let timeout: TimeInterval
    
    public init(url: URL, method: String = "GET",
                headers: [String: String] = [:],
                timeout: TimeInterval = 30) {
        self.url = url
        self.method = method
        self.headers = headers
        self.timeout = timeout
    }
}

#if swift(>=6.0)
// Swift 6: Sendable explicit, di-enforce compiler
extension RequestConfig: Sendable {}
#else
// Swift 5: @unchecked Sendable karena struct dengan value types sudah aman
// tapi compiler tidak enforce ini
extension RequestConfig: @unchecked Sendable {}
#endif

// Actor untuk thread-safety: syntax sama di Swift 5 dan 6
// Tapi di Swift 6 enforcement lebih ketat
public actor NetworkClient {
    private let session: URLSession
    private var activeTasks: [UUID: Task<Data, Error>] = [:]
    
    public init(configuration: URLSessionConfiguration = .default) {
        self.session = URLSession(configuration: configuration)
    }
    
    public func fetch(_ config: RequestConfig) async throws -> Data {
        var request = URLRequest(url: config.url, timeoutInterval: config.timeout)
        request.httpMethod = config.method
        config.headers.forEach { request.setValue($1, forHTTPHeaderField: $0) }
        
        let (data, response) = try await session.data(for: request)
        
        guard let http = response as? HTTPURLResponse,
              (200...299).contains(http.statusCode) else {
            throw NetworkError.invalidResponse
        }
        
        return data
    }
}

public enum NetworkError: Error, Sendable {
    case invalidResponse
    case timeout
    case noConnection
}

Use Case 2: App dengan Target Campuran

swift
MyApp (Xcode Project)
├── MyAppCore.framework   → Swift 6 (business logic)
├── MyAppUI.framework     → Swift 5 + StrictConcurrency warning
└── MyApp target          → Swift 5 (app delegate, scene delegate)
swift
// MyAppCore — Swift 6, fully migrated
// Sources/MyAppCore/UserRepository.swift

import Foundation

// Swift 6: actor + Sendable sudah di-enforce
public actor UserRepository {
    private var cache: [String: User] = [:]
    
    public func fetchUser(id: String) async throws -> User {
        if let cached = cache[id] { return cached }
        let user = try await loadFromAPI(id: id)
        cache[id] = user
        return user
    }
    
    private func loadFromAPI(id: String) async throws -> User {
        // ... network call
        return User(id: id, name: "Test")
    }
}

public struct User: Sendable {
    public let id: String
    public let name: String
    
    public init(id: String, name: String) {
        self.id = id
        self.name = name
    }
}
swift
// MyAppUI — Swift 5 + StrictConcurrency = targeted (warning only)
// Sources/MyAppUI/UserListViewModel.swift

import Foundation
import MyAppCore

// Warning di Swift 5 mode: "class should be marked @MainActor"
// Tidak error — kita lihat dan fix satu per satu
@MainActor  // fix warning ini
public final class UserListViewModel: ObservableObject {
    @Published public private(set) var users: [User] = []
    @Published public private(set) var isLoading = false
    
    private let repository: UserRepository
    
    public init(repository: UserRepository) {
        self.repository = repository
    }
    
    public func load(userID: String) async {
        isLoading = true
        defer { isLoading = false }
        
        do {
            // UserRepository adalah actor dari MyAppCore (Swift 6)
            // Crossing isolation boundary — butuh await
            let user = try await repository.fetchUser(id: userID)
            users = [user]
        } catch {
            print("Error: \(error)")
        }
    }
}
swift
// MyApp target — Swift 5, belum dimigrasikan
// App/SceneDelegate.swift

import UIKit
import MyAppUI
import MyAppCore

// Di Swift 5 mode: tidak ada error concurrency, tapi ada warning dengan StrictConcurrency
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    
    func scene(_ scene: UIScene,
                willConnectTo session: UISceneSession,
                options: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        
        let repository = UserRepository()  // OK: actor, Sendable
        let viewModel = UserListViewModel(repository: repository)  // OK: @MainActor class
        
        let vc = UserListViewController(viewModel: viewModel)
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = UINavigationController(rootViewController: vc)
        window?.makeKeyAndVisible()
    }
}

Use Case 3: SPM Package dengan Conditional Features

swift
// Package.swift — library dengan feature flags untuk Swift 5/6
// swift-tools-version: 5.10

import PackageDescription

let swiftSettings: [SwiftSetting] = [
    // Base: kompatibel Swift 5
    .swiftLanguageVersion(.v5),
    
    // Opt-in fitur yang sudah stabil
    .enableUpcomingFeature("StrictConcurrency"),
    .enableUpcomingFeature("ExistentialAny"),
]

let package = Package(
    name: "DataPipeline",
    platforms: [.iOS(.v16), .macOS(.v13)],
    products: [
        .library(name: "DataPipeline", targets: ["DataPipeline"]),
        .library(name: "DataPipelineCompat", targets: ["DataPipelineCompat"]),
    ],
    targets: [
        // Core — Swift 6
        .target(
            name: "DataPipelineCore",
            swiftSettings: [.swiftLanguageVersion(.v6)]
        ),
        
        // Public API — Swift 5 + compat layer
        .target(
            name: "DataPipeline",
            dependencies: ["DataPipelineCore"],
            swiftSettings: swiftSettings
        ),
        
        // Compatibility wrapper untuk Swift 5 consumer
        .target(
            name: "DataPipelineCompat",
            dependencies: ["DataPipeline"],
            swiftSettings: [.swiftLanguageVersion(.v5)]
        ),
        
        // Tests — Swift 6 untuk catch issues lebih awal
        .testTarget(
            name: "DataPipelineTests",
            dependencies: ["DataPipeline"],
            swiftSettings: [.swiftLanguageVersion(.v6)]
        ),
    ]
)