Section 16/171 menit

16. Real Use Cases

16. Real Use Cases

Use Case 1: Modular App dengan Feature Packages

Struktur app dengan 3 feature module + 2 core module.

swift
// Modules/CoreNetworking/Package.swift
// swift-tools-version:5.10
import PackageDescription

let package = Package(
    name: "CoreNetworking",
    platforms: [.iOS(.v16)],
    products: [
        .library(name: "CoreNetworking", targets: ["CoreNetworking"]),
    ],
    targets: [
        .target(name: "CoreNetworking"),
        .testTarget(name: "CoreNetworkingTests", dependencies: ["CoreNetworking"]),
    ]
)
swift
// Modules/CoreNetworking/Sources/CoreNetworking/HTTPClient.swift
import Foundation

public actor HTTPClient {
    private let session: URLSession

    public init(session: URLSession = .shared) {
        self.session = session
    }

    public func get<T: Decodable & Sendable>(
        _ url: URL,
        as type: T.Type
    ) async throws -> T {
        let (data, _) = try await session.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}
swift
// Modules/LoginFeature/Package.swift
import PackageDescription

let package = Package(
    name: "LoginFeature",
    platforms: [.iOS(.v16)],
    products: [
        .library(name: "LoginFeature", targets: ["LoginFeature"]),
    ],
    dependencies: [
        .package(path: "../CoreNetworking"),
        .package(path: "../CoreUI"),
    ],
    targets: [
        .target(
            name: "LoginFeature",
            dependencies: ["CoreNetworking", "CoreUI"]
        ),
        .testTarget(name: "LoginFeatureTests", dependencies: ["LoginFeature"]),
    ]
)
swift
// MyApp.xcodeproj — depend ke ketiga feature package via "Add Local"
// Di app shell:
import LoginFeature
import HomeFeature
import ProfileFeature

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            AppCoordinator()
        }
    }
}

Keputusan desain:

  • Core module tidak tahu feature → tidak ada cyclic dep.
  • Feature → Core, satu arah.
  • App shell yang compose semua feature.
  • Setiap module bisa di-test isolated.

Use Case 2: Library Open Source dengan Multi-Product

Library design system yang punya core + ekstensi opsional.

swift
// swift-tools-version:5.10
import PackageDescription

let package = Package(
    name: "MyDesignSystem",
    platforms: [.iOS(.v16), .macOS(.v13)],
    products: [
        // Core: minimum required
        .library(name: "DesignTokens", targets: ["DesignTokens"]),
        // Optional SwiftUI components
        .library(name: "DesignSystemSwiftUI", targets: ["DesignSystemSwiftUI"]),
        // Optional UIKit bridges
        .library(name: "DesignSystemUIKit", targets: ["DesignSystemUIKit"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "DesignTokens",
            resources: [.process("Resources")]
        ),
        .target(
            name: "DesignSystemSwiftUI",
            dependencies: ["DesignTokens"]
        ),
        .target(
            name: "DesignSystemUIKit",
            dependencies: ["DesignTokens"]
        ),
        .testTarget(name: "DesignTokensTests", dependencies: ["DesignTokens"]),
        .testTarget(name: "DesignSystemSwiftUITests", dependencies: ["DesignSystemSwiftUI"]),
    ]
)

Keputusan desain:

  • Consumer SwiftUI tidak perlu compile UIKit code, dan sebaliknya.
  • DesignTokens (warna, font, spacing) bisa di-share untuk app extension dengan budget kecil.
  • Resource (font, image) hanya di-include di module yang butuh.

Use Case 3: Codegen Plugin untuk API Client

Plugin yang generate API client dari OpenAPI spec saat build.

swift
// swift-tools-version:5.10
let package = Package(
    name: "MyAPI",
    products: [
        .library(name: "MyAPI", targets: ["MyAPI"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-openapi-urlsession.git", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "MyAPI",
            dependencies: [
                .product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
                .product(name: "OpenAPIURLSession", package: "swift-openapi-urlsession"),
            ],
            plugins: [
                .plugin(name: "OpenAPIGenerator", package: "swift-openapi-generator")
            ]
        ),
    ]
)
swift
# Sources/MyAPI/openapi-generator-config.yaml
generate:
  - types
  - client
accessModifier: public
swift
# Sources/MyAPI/openapi.yaml (OpenAPI 3.x spec)
openapi: "3.1.0"
info:
  title: My API
  version: 1.0.0
paths:
  /users:
    get:
      operationId: listUsers
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
swift
// Sources/MyAPI/APIClient.swift — kode user-written
import OpenAPIRuntime
import OpenAPIURLSession
import Foundation

public actor APIClient {
    private let client: Client

    public init(serverURL: URL) throws {
        self.client = Client(
            serverURL: serverURL,
            transport: URLSessionTransport()
        )
    }

    public func listUsers() async throws -> [Components.Schemas.User] {
        let response = try await client.listUsers()
        switch response {
        case .ok(let ok):
            return try ok.body.json
        case .undocumented(let status, _):
            throw APIError.unexpectedStatus(status)
        }
    }
}

public enum APIError: Error {
    case unexpectedStatus(Int)
}

Keputusan desain:

  • Spec OpenAPI = source of truth. Client code auto-regenerate setiap spec berubah.
  • Build plugin dijalankan otomatis — tidak ada manual codegen step.
  • Type-safe: response User ter-decode otomatis sesuai schema.

Use Case 4: XCFramework Distribution untuk SDK Closed-Source

SDK pihak ketiga yang di-distribute sebagai binary.

swift
// swift-tools-version:5.10
import PackageDescription

let package = Package(
    name: "AnalyticsSDK",
    platforms: [.iOS(.v15)],
    products: [
        .library(name: "AnalyticsSDK", targets: ["AnalyticsSDKWrapper"]),
    ],
    targets: [
        .binaryTarget(
            name: "AnalyticsCore",
            url: "https://cdn.example.com/sdk/v2.3.1/AnalyticsCore.xcframework.zip",
            checksum: "f3d4a8b2c1e9f7a6b5d4c3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2"
        ),
        .target(
            name: "AnalyticsSDKWrapper",
            dependencies: ["AnalyticsCore"],
            path: "Sources/Wrapper"
        ),
    ]
)
swift
// Sources/Wrapper/AnalyticsSDK.swift — Swift wrapper di sekitar binary
@_exported import AnalyticsCore

public extension AnalyticsCore.Tracker {
    /// Swift-idiomatic wrapper around Obj-C API
    static func track(event name: String, properties: [String: Any] = [:]) {
        AnalyticsCore.Tracker.shared.trackEvent(name, properties: properties)
    }
}

Keputusan desain:

  • Binary di CDN dengan checksum → integrity verified.
  • Wrapper target memungkinkan API improvement tanpa rebuild binary.
  • @_exported import membuat consumer cukup import AnalyticsSDK, tidak perlu import AnalyticsCore terpisah.