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)]
),
]
)