WWDC Quick Look 💓 By SwiftGGTeam
Use Xcode for server-side development

Use Xcode for server-side development

元の動画を見る

ハイライト

Xcode 14 は、同じワークスペースでの iOS クライアントと Swift サーバー側アプリケーションの同時開発をサポートしており、Vapor などの Web フレームワークを使用して HTTP サービスを構築し、Swift パッケージを共有してエンドツーエンドの Swift フルスタック開発を実現することでクライアントとサーバーの間でデータ モデルを再利用します。

主要内容

多くのアプリケーションは 1 台のデバイスで開始され、ユーザー数の増加に応じて複数のデバイスに拡張する必要があり、最終的にはバックグラウンド タスク、計算集約型の作業、または集中データ ストレージを処理するサーバー コンポーネントが必要になることがよくあります。

従来のアプローチは、クライアント側で Swift を使用し、サーバー側で他の言語を使用することです。これにより、テクノロジー スタックの断片化、コードの重複、チームのコラボレーション コストの高騰などの問題が発生しました。

Swift は一般的なプログラミング言語として、クライアントとサーバーのテクノロジー スタックを統合できます。 Xcode 14 では、この機能がさらに強化されています。iOS アプリと Swift サーバー アプリケーションを同じワークスペースで同時に開発し、コードを共有し、統合的にデバッグできます。

サーバーアプリケーションの基本構造

Swift サーバー アプリケーションは、実行可能ターゲット (01:36) を定義する Swift パッケージです。

// swift-tools-version: 5.7
import PackageDescription

let package = Package(
    name: "MyServer",
    platforms: [.macOS("12.0")],
    products: [
        .executable(name: "MyServer", targets: ["MyServer"]),
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")),
    ],
    targets: [
        .executableTarget(
            name: "MyServer",
            dependencies: [.product(name: "Vapor", package: "vapor")]
        ),
        .testTarget(
            name: "MyServerTests",
            dependencies: ["MyServer"]
        ),
    ]
)

キーポイント:

  • platformsmacOS 12.0、Mac または Linux 上で実行されるサーバー アプリケーションを指定します
  • products実行可能ファイルを宣言する
  • Vapor フレームワークに依存して HTTP ルーティングとリクエスト応答を処理します

サーバー側のコードの使用法@mainエントリ ポイント (02:00) をマークします。

import Vapor

@main
public struct MyServer {
    public static func main() async throws {
        let webapp = Application()
        webapp.get("greet", use: Self.greet)
        webapp.post("echo", use: Self.echo)
        try webapp.run()
    }

    static func greet(request: Request) async throws -> String {
        return "Hello from Swift Server"
    }

    static func echo(request: Request) async throws -> String {
        if let body = request.body.string {
            return body
        }
        return ""
    }
}

キーポイント:

  • ApplicationVapor が提供する Web アプリケーションのタイプです
  • get/postHTTP エンドポイントを定義する
  • 処理機能用async throws、当然、Swift 同時実行をサポートします。

ローカルテスト

Xcode で MyServer スキームを選択し、ターゲットとして「My Mac」を選択して、「実行」をクリックします。コンソールにはサーバーの起動情報が表示されます。

Server starting on http://127.0.0.1:8080

カールでエンドポイントをテストします (03:42):

curl http://127.0.0.1:8080/greet; echo
curl http://127.0.0.1:8080/echo --data "Hello from WWDC 2022"; echo

iOS アプリからの通話

クライアント抽象化レイヤーを作成し、サーバー API を Swift メソッドにカプセル化します (04:10):

import Foundation

struct MyServerClient {
    let baseURL = URL(string: "http://127.0.0.1:8080")!

    func greet() async throws -> String {
        let url = baseURL.appendingPathComponent("greet")
        let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
        guard let responseBody = String(data: data, encoding: .utf8) else {
            throw Errors.invalidResponseEncoding
        }
        return responseBody
    }

    enum Errors: Error {
        case invalidResponseEncoding
    }
}

SwiftUI ビューで呼び出されます (05:00):

import SwiftUI

struct ContentView: View {
    @State var serverGreeting = ""

    var body: some View {
        Text(serverGreeting)
            .padding()
            .task {
                do {
                    let client = MyServerClient()
                    self.serverGreeting = try await client.greet()
                } catch {
                    self.serverGreeting = String(describing: error)
                }
            }
    }
}

キーポイント:

  • ネットワークリクエストは当然非同期です。async/await対処する
  • .taskビューが表示されたときに非同期タスクを自動的に開始する修飾子
  • iOS アプリを実行する前に、必ず Xcode でサーバーを起動してください

詳細

フード トラックの例: 完全なサーバー側アプリケーション

Session はフード トラック アプリを使用してフルスタック開発をデモンストレーションします。サーバーはドーナツ メニュー API を提供し、iOS クライアントはメニューを表示します。

データモデル

サーバーとクライアントには一貫したデータ モデルが必要です (09:51):

enum Model {
    struct Donut: Codable {
        var id: Int
        var name: String
        var date: Date
        var dough: Dough
        var glaze: Glaze?
        var topping: Topping?
    }

    struct Dough: Codable {
        var name: String
        var description: String
        var flavors: FlavorProfile
    }

    struct Glaze: Codable {
        var name: String
        var description: String
        var flavors: FlavorProfile
    }

    struct Topping: Codable {
        var name: String
        var description: String
        var flavors: FlavorProfile
    }

    public struct FlavorProfile: Codable {
        var salty: Int?
        var sweet: Int?
        var bitter: Int?
        var sour: Int?
        var savory: Int?
        var spicy: Int?
    }
}

キーポイント:

  • すべてのモデルに従うCodable、JSON エンコードおよびデコード用
  • フレーバープロファイルは、オプションの整数を使用してさまざまなフレーバー強度を表します。
  • これらのモデルは最初に iOS アプリからコピーされ、後で共有パッケージに抽出されます。

サーバー側のアーキテクチャ

サーバー側の階層化設計:

import Foundation
import Vapor

@main
struct FoodTruckServerBootstrap {
    public static func main() async throws {
        let foodTruckServer = FoodTruckServer()
        try await foodTruckServer.bootstrap()

        let webapp = Application()
        webapp.get("donuts", use: foodTruckServer.listDonuts)
        try webapp.run()
    }
}

struct FoodTruckServer {
    private let storage = Storage()

    func bootstrap() async throws {
        try await self.storage.load()
    }

    func listDonuts(request: Request) async -> Response.Donuts {
        let donuts = await self.storage.listDonuts()
        return Response.Donuts(donuts: donuts)
    }

    enum Response {
        struct Donuts: Content {
            var donuts: [Model.Donut]
        }
    }
}

キーポイント:

  • FoodTruckServerこれはビジネス ロジック層であり、HTTP を直接処理しません。
  • Storageこれはデータ アクセス層であり、アクターを使用してスレッドの安全性を確保します。
  • Response.DonutsヴェイパーさんをフォローするContentプロトコル、JSON として自動的にエンコードされる

ストレージ層: アクタを使用して可変状態を保護する

ストレージ層は、JSON ファイルをデータ ソースとして使用します。actorデータ競合の回避 (12:30):

actor Storage {
    let jsonDecoder: JSONDecoder
    var donuts = [Model.Donut]()

    init() {
        self.jsonDecoder = JSONDecoder()
        self.jsonDecoder.dateDecodingStrategy = .iso8601
    }

    func load() throws {
        guard let path = Bundle.module.path(forResource: "menu", ofType: "json") else {
            throw Errors.menuFileNotFound
        }
        guard let data = FileManager.default.contents(atPath: path) else {
            throw Errors.failedLoadingMenu
        }
        self.donuts = try self.jsonDecoder.decode([Model.Donut].self, from: data)
    }

    func listDonuts() -> [Model.Donut] {
        return self.donuts
    }

    enum Errors: Error {
        case menuFileNotFound
        case failedLoadingMenu
    }
}

キーポイント:

  • actor必ずdonuts配列へのすべてのアクセスはシリアルです
  • ロックやディスパッチキューを手動で使用する必要はありません
  • Bundle.moduleリソース ファイル用に SwiftPM によって生成されるアクセサーです
  • iso8601日付デコード戦略は標準の日付形式を処理します

リソース ファイルはパッケージ マニフェスト (12:23) で宣言されます。

.executableTarget(
    name: "FoodTruckServer",
    dependencies: [.product(name: "Vapor", package: "vapor")],
    resources: [
        .copy("menu.json")
    ]
)

テストサーバー API

サーバーを起動した後、curl と jq でテストします (14:42):

# 查看完整菜单
curl http://127.0.0.1:8080/donuts | jq .

# 只提取甜甜圈名称
curl http://127.0.0.1:8080/donuts | jq '.donuts[] .name'

共有データモデル

クライアントとサーバーは最初、それぞれデータ モデル コードのコピーを保持します。より良いアプローチは、共有 Swift パッケージに抽出することです。

1.「Shared」という名前の Swift パッケージを作成し、Xcode ワークスペースに配置します。 2.置くModelコードが共有パッケージに移動されました 3. 共有依存関係をサーバー パッケージと iOS アプリの両方に追加します 4. 両端で共有モデル定義を使用する

これを行う利点:

  • コードの重複を避ける
  • シリアル化はより安全であり、モデルが変更されると両端が同期して更新されます。
  • コンパイル時間をチェックしてデータ構造の一貫性を確保します

ストレージ戦略の選択

セッションでは、いくつかのデータ ストレージ ソリューションについて言及しました (11:12):

静的ファイル: データがほとんど変更されない場合、または手動で維持される場合は、JSON/CSV ファイルを直接使用します。メニュー、構成、その他のシナリオに適しています。

iCloud: 別のサーバーを展開する必要がなく、CloudKit API を使用して iOS アプリからユーザー データまたはグローバル データ セットを直接使用できます。

データベース: 動的データまたはトランザクション データ。 Swift オープン ソース コミュニティは、PostgreSQL、MySQL、MongoDB、Redis、DynamoDB などのさまざまなデータベース ドライバーを提供します。

重要ポイント

1.リアルタイム コラボレーション ホワイトボード

何をすべきか: Swift サーバー + SwiftUI クライアントを使用して、ストロークの同期をサポートする複数人によるリアルタイムの共同作業用ホワイトボードを作成します。

実行する価値がある理由: WebSocket サーバーは Swift で書かれており、ストローク データ モデルを iOS/macOS クライアントと共有し、1 セットのコードを使用してシステム全体を理解します。

開始方法: Vapor の WebSocket 拡張機能を使用してライブ接続を処理します。actorクライアントが使用する部屋のステータスを管理するURLSessionWebSocketTask接続する。

2.個人データ同期サービス

対処方法: ユーザー データを自己ホスト型サーバーに保存して、プライベート クラウド同期機能を既存の iOS アプリに追加します。

実行する価値がある理由: Swift を使用してサーバー側を作成すると、クライアント側を再利用できるCodableモデルでは、2 セットのデータ構造定義を維持する必要はありません。

開始方法: データ モデルを保存する共有パッケージを定義し、サーバーで Vapor を使用して CRUD API を提供し、クライアントで Vapor を使用します。URLSession + JSONDecoder消費。

3.ホームメディアサーバー

やるべきこと: Mac または Raspberry Pi で Swift サーバーを実行し、家族の写真とビデオのライブラリを管理し、閲覧と再生のための iOS アプリを管理します。

実行する価値がある理由: SwiftAVFoundation知識はサーバー側で部分的に再利用され、メディアのメタデータとトランスコーディング ロジックを処理できます。

開始方法: Vapor を使用してメディア ファイル ストリーミング API を提供します。FileManagerメディア ディレクトリをスキャンしてインデックスを生成し、クライアントは SwiftUI を使用してグリッドを表示します。

4. IoT デバイス管理センター

対処法: Swift サーバーを使用して家庭用センサー データ (温度、湿度) を収集すると、iOS アプリに過去の傾向とリアルタイムのアラームが表示されます。

実行する価値がある理由: Swift サーバーは、以下を使用して軽量デバイス上で実行できます。async/await多数の同時デバイス接続を処理します。

開始方法: センサー データ モデル共有パッケージを定義します。サーバーは UDP/TCP を使用してデバイス レポートを受信し、それらを SQLite または InfluxDB に保存し、クエリ API を提供します。

5.チーム内部ツール プラットフォーム

やるべきこと: Swift フルスタック開発チームの内部ツール (コード レビュー アシスタント、リリース管理システムなど) を使用します。

実行する価値がある理由: チームはすでに Swift に慣れており、フルスタックの統合テクノロジー スタックによりメンテナンス コストが削減され、Xcode ワークスペースはフロントエンドとバックエンドを同時にデバッグします。

開始方法: Vapor を使用して REST API を提供し、GitHub/GitLab Webhook を統合し、クライアントで SwiftUI を使用して ToDo 項目と統計グラフを表示します。

関連セッション

  • Swift Package プラグインの紹介 — プラグインを使用してサーバー側のコード生成とデプロイメントのワークフローを自動化します
  • Swift Concurrency — 深い理解async/awaitそしてactor、同時実行性の高いサーバー コードを作成する
  • Swift Regex — Swift Regex を使用してリクエスト パラメーター、ルート マッチング、ログ分析を解析します

コメント

GitHub Issues · utterances