ハイライト
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