Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
わいわいswiftc#35夢が広がる!コード生成でどこでもSwift
Search
Iceman
April 25, 2022
Programming
490
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
わいわいswiftc#35夢が広がる!コード生成でどこでもSwift
Iceman
April 25, 2022
More Decks by Iceman
See All by Iceman
わいわいswift#39 Swiftの型をTypeScriptで表す
sidepelican
0
360
元ゲーム開発者が贈る描画パフォーマンス改善 / Rendering performance improvement from a game developer
sidepelican
4
1.9k
わいわいswiftc#19Genericsの特殊化
sidepelican
0
500
わいわいswiftc#17Genericsの特殊化
sidepelican
0
110
SwiftUI: 更新検知と値の生存期間
sidepelican
2
1.2k
クックパッドiOSアプリのパフォーマンス改善
sidepelican
0
820
DispatchQueue.syncが動作するスレッド
sidepelican
0
400
Other Decks in Programming
See All in Programming
Jetpack Compose メカニズム
skydoves
0
450
テストを司るデーモンに会いに行く 〜隔離した仮想マシンでテストを通すまで〜
h1d3mun3
1
540
FreeBSDでZabbixを動かす
kenkino
0
310
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
180
速く作れる。その次は、速く確かめられる開発へ 〜AIネイティブ開発を支える、Shift Down〜 / Can build fast. Next, moving to development where we can verify fast.
rkaga
5
3.5k
Are APIs Still Relevant in the AI Era?
soyuka
0
280
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
190
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
7.6k
選挙速報を多くのユーザーへ 届ける Live Activities 設計
hamayokokuririn
0
150
WebAssembly in Android Apps 〜 WASMはJNIの夢を見るか
keiji
1
110
モジュールの視点からSwiftを読み解く #iosdc
s_shimotori
0
180
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
200
Featured
See All Featured
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
12k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
420
HDC tutorial
michielstock
2
870
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
660
YesSQL, Process and Tooling at Scale
rocio
174
15k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
500
Done Done
chrislema
186
16k
Designing for Performance
lara
611
70k
Un-Boring Meetings
codingconduct
0
420
The agentic SEO stack - context over prompts
schlessera
0
940
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.8k
Transcript
わいわいswiftc #35 夢が広がる!コード生成でどこでもSwift Twitter @iceman5499 2022年4月25日 1
既存コード生成技術の紹介 ステンシルを書いてテンプレート出力する系 SwiftGen/SwiftGen krzysztofzablocki/Sourcery 単一の高度な機能を提供する系 uber/mockolo uber/needle SwiftGen以外は全てSwiftSyntaxを用いている 2
SwiftSyntaxの課題 多くのコード生成ライブラリはSwiftSyntaxを利用しているが、Xcodeとバージョンを揃えて 使う必要があって地味に大変 → BetaなXcodeを使用しているなどで利用できない → Xcodeから実行する際に環境変数の指定が必要 3
使いやすさの問題 stencilファイルの難しさ(SwiftGen, Sourcery) 独自文法を勉強するのが大変 できない表現があったりして、代替案を頑張って模索する regexが使えないなど: https://github.com/SwiftGen/StencilSwiftKit/pull/123 魔術的なコードになりやすい 4
使いやすさの問題 自由度の課題 ライブラリが提供する表現力の範囲でしかコード生成できない オプションで切り替えられる範囲にも限度がある → ライブラリが想定する使い方の範囲で強く効果を発揮する → 自分のプロジェクトのほうをライブラリの思想に合わせて設計する必要がある 5
BinarySwiftSyntax & SwiftTypeReader BinarySwiftSyntax ローカルのXcode依存を回避 SwiftTypeReader コード生成器を自作しやすくする 6
作例紹介 7
CodableToTypeScript https://github.com/omochi/CodableToTypeScript Swiftの型定義をTypeScriptの型定義に変換する 8
CodableToTypeScript 例1: シンプルなCodable public struct Foo: Codable { public var
bar: Int? public var baz: [String] } → export type Foo = { bar?: number; baz: string[]; }; 9
CodableToTypeScript 例2: 文字列enum public enum Language: String, Codable { case
ms case en case ja } → export type Language = "ms" | "en" | "ja"; 10
CodableToTypeScript 例3: 値付きenum public enum FilterItem: Codable, Equatable { case
name(String) case email(String) } ~~~Decode 関数も自動で生成 される kind を追加することで switchにおける網羅チェック とsmart castを有効にしている → export type FilterItemJSON = { name: { _0: string; }; } | { email: { _0: string; }; }; export type FilterItem = { kind: "name"; name: { _0: string; }; } | { kind: "email"; email: { _0: string; }; }; export function FilterItemDecode(json: FilterItemJSON): FilterItem { if ("name" in json) { return { "kind": "name", name: json.name }; } else if ("email" in json) { return { "kind": "email", email: json.email }; } else { throw new Error("unknown kind"); } } 11
CodableToTypeScript 使用例: switch (filter.kind) { case "name": const name =
filter.name._0; // .name をエラー無しに参照できる ... case "email": const email = filter.email._0; // .email をエラー無しに参照できる ... } smart castによってcaseごとの値を型安全に取り出せる 12
CodableToTypeScript SwiftサーバとTypeScriptクライアントな環境において、Swift側の型定義を変更するだけ でTS側もコンパイルエラーになってくれる enumのcaseをunionにしたり値付きenumが使えたりと、Swiftの表現力をそのまま 利用できて便利 .proto や .graphql などの専用の定義ファイルは不要 [T]
を T[] に変換したり、 T? を T|undefined として変換できる (ある程度は)Genericsにも対応 13
使い方 CodableToTypeScript単体はライブラリなので、自前でコード生成用ターゲットを作って そこから使う // Package.swift .package(url: "https://github.com/omochi/CodableToTypeScript", branch: "main"), ...
.executableTarget( name: "CodeGenStage2", dependencies: [ "CodableToTypeScript", ] ), 14
使い方 // main.swift import SwiftTypeReader import CodableToTypeScript let module =
try SwiftTypeReader.Reader().read(file: ...).module let generate = CodableToTypeScript.CodeGenerator(typeMap: .default) for swiftType in module.types { let tsCode = try generate(type: swiftType) _ = tsCode.description // TypeScript コードそのままの文字列になっている } SwiftTypeReaderで読み取った型をCodableToTypeScriptに渡す 15
CallableKit https://github.com/sidepelican/CallableKit サーバ上のSwift関数をクライアントにasync関数として出荷する descriptionなにもなくてごめんなさい 16
CallableKit 定義protocolから、サーバ用コードとクライアント用コードが生成される 例: 定義protocol public protocol EchoServiceProtocol { func hello(request:
EchoHelloRequest) async throws -> EchoHelloResponse } public struct EchoHelloRequest: Codable, Sendable { public var name: String } public struct EchoHelloResponse: Codable, Sendable { public var message: String } 17
CallableKit 例: サーバ用ルーティング実装(生成コード) import APIDefinition // 定義ファイルはそのままモジュールとしても利用する import Vapor struct
EchoServiceProvider<RequestHandler: RawRequestHandler, Service: EchoServiceProtocol>: RouteCollection { var requestHandler: RequestHandler var serviceBuilder: (Request) -> Service init(handler: RequestHandler, builder: @escaping (Request) -> Service) { self.requestHandler = handler self.serviceBuilder = builder } func boot(routes: RoutesBuilder) throws { routes.group("Echo") { group in group.post("hello", use: requestHandler.makeHandler(serviceBuilder) { s in try await s.hello() }) } } } RouteCollection なので、Vaporの RoutesBuilder にそのままregisterできる 18
CallableKit 例: クライアント用スタブ実装(生成コード) import APIDefinition public struct EchoServiceStub: EchoServiceProtocol, Sendable
{ private let client: StubClientProtocol public init(client: StubClientProtocol) { self.client = client } public func hello(request: EchoHelloRequest) async throws -> EchoHelloResponse { return try await client.send(path: "Echo/hello") } } 19
生成コードの役割は型をつけるだけなので、送信部分の実装詳細には関与していない 雰囲気はgRPCと同じ gRPCよりはかなり薄くて、通信の詳細などは規定せずあくまでインターフェース を定義するだけ Swift Distributed Actorsのように、サーバ上のasync関数を呼び出せるようにする try await echoService.hello(request:
.init(name: "Foo")) 20
パッケージ構造 . ├── APIDefinition │ └── Sources │ └── APIDefinition
// 定義だけで実装はなし │ └── Echo.swift ├── APIServer │ └── Sources │ ├── Service // Service の具体的な実装。依存にサーバ用モジュールはなし │ │ └── EchoService.swift │ └── Server // Vapor に依存し、サーバを起動する │ ├── EchoProvider.gen.swift │ └── main.swift ├── ClientApp │ └── Sources │ └── APIClient │ └── EchoStub.gen.swift 21
現在はHTTPの通信にVaporを利用しているが、直接依存しているわけではないので将来 的にVapor以外のフレームワークにも切り替えられる クライアントではただのprotocolとして見えているため、モック実装などへの差し替え が容易 サンプルプロジェクト: https://github.com/sidepelican/CallableKit/tree/main/example 22
Typescript版クライアント CodableToTypeScriptと組み合わせて、TypeScriptクライアントもコード生成 23
Typescript版クライアント 例: TS版クライアント用スタブ実装(生成コード) import { IRawClient } from "./common.gen"; export
interface IEchoClient { hello(request: EchoHelloRequest): Promise<EchoHelloResponse> } class EchoClient implements IEchoClient { rawClient: IRawClient; constructor(rawClient: IRawClient) { this.rawClient = rawClient; } async hello(request: EchoHelloRequest): Promise<EchoHelloResponse> { return await this.rawClient.fetch({}, "Echo/hello") as EchoHelloResponse } } export const buildEchoClient = (raw: IRawClient): IEchoClient => new EchoClient(raw); export type EchoHelloRequest = { name: string; }; export type EchoHelloResponse = { message: string; }; 24
CodableToTypeScript Swiftの型をTypeScriptの型に変換できる CallableKit Swift protocolを任意の言語のinterfaceに変換できる → WebAssembly × TypeScriptにも応用可能 25
WasmCallableKit https://github.com/sidepelican/WasmCallableKit Swiftの型をそのままexportできるWasmライブラリを作成 descriptionなにもなくてごめんなさい 26
WasmCallableKit WasmビルドされたSwift関数をTSから呼び出せる 例: // WasmExports.swift protocol WasmExports { static func
hello(name: String) -> String } // main.swift struct Foo: WasmExports { static func hello(name: String) -> String { "Hello, \(name) from Swift" } } WasmCallableKit.setFunctionList(Foo.functionList) → export type FooExports = { hello: (name: string) => string, }; console.log(swift.hello("world")) // > Hello, world from Swift 27
もちろん、CodableToTypeScriptで変換できるSwiftの型なら何でもやりとりできる protocol WasmExports { static func newGame() -> GameID static
func putFence(game: GameID, position: FencePoint) throws static func movePawn(game: GameID, position: PawnPoint) throws static func aiNext(game: GameID) throws static func currentBoard(game: GameID) throws -> Board static func deleteGame(game: GameID) } ↓ export type WasmLibExports = { newGame: () => GameID, putFence: (game: GameID, position: FencePoint) => void, movePawn: (game: GameID, position: PawnPoint) => void, aiNext: (game: GameID) => void, currentBoard: (game: GameID) => Board, deleteGame: (game: GameID) => void, }; 28
WasmCallableKitの仕組み 文字列をやりとりできるように最低限のランタイムライブラリの用意 Wasmはそのままだと数値型しか直接やりとりできない SwiftTypeReaderとCodableToTypeScriptでTS用の型定義 JS ⇔ Swift間で引数と返り値をJSON文字列としてやりとりする tsランタイム: https://github.com/sidepelican/WasmCallableKit/blob/main/Codegen/Sources/Codegen/templates/SwiftRuntime.ts swiftランタイム:
https://github.com/sidepelican/WasmCallableKit/blob/main/Sources/WasmCallableKit/WasmCallableKit.swift 29
使用例 Swift Quoridor: https://swiftwasmquoridor.iceman5499.work Quoridor(コリドール)というボードゲームとそのAIをSwiftで実装 UIだけReact リポジトリ: https://github.com/sidepelican/SwiftWasmQuoridor 30
JavaScriptKitとの比較? JavaScriptKitはSwiftからJS関数を呼び、SwiftがJSを利用する形になっている。これは Reactのような、JSフレームワークからSwiftを利用したい場合に使いづらかった 単純にやってみたかった 課題 関数を呼び出すたびにJSON文字列との変換が入るのでめちゃくちゃ遅い Reactの場合、1ビルド中に100回程度Swift関数を呼び出すとそのオーバヘッドだけ で遅延を体感できる シリアライズをより軽量な方法で行う、数値型はそのまま渡す、などの工夫が必要そう 31
ここまではブラウザにおける話。 ブラウザからSwiftのWebAPIやWasmのSwift関数を利用できるようになった。 JS上でSwiftを使いたい需要、他には・・・? 32
Cloud Functions for Firebase上でSwift関数を実行 33
Cloud Functions for Firebase上でSwift関数を実行 ブラウザのWasmでSwift関数が使えるなら、Nodeでも動かせるはず サンプル: https://github.com/sidepelican/CFSwiftWasmExample 例: export const
hello = functions.https.onRequest(async (request, response) => { const name = request.query["name"] as string ?? "world"; response.send(swift.hello(name)); }); 34
Cloud Functions for Firebase上でSwift関数を実行 1. WASIのセットアップ Cloud Functions上のNodeではWASIが利用できない( --experimental-wasi- unstablre-preview0
を有効にする方法がない?)ので、 @wasmer/wasi を使っ てWASIを構築する const wasi = new WASI(); 35
2. 通常のWebAssembly利用時のボイラープレート通りにセットアップ const swift = new SwiftRuntime(); const wasmPath =
path.join(__dirname, 'Gen/MySwiftLib.wasm'); const module = new WebAssembly.Module(fs.readFileSync(wasmPath)); const instance = new WebAssembly.Instance(module, { ...wasi.getImports(module), ...swift.callableKitImpodrts, }); swift.setInstance(instance); wasi.start(instance); return bindMySwiftLib(swift); 36
Cloud Functions for FirebaseでSwiftWasmを使うことは実用的か? Webと違い、バイナリサイズを(そこまで)気にしなくて良い NIOがないため、既存のサーバ用Swiftコードの多くが利用できない NIOのWasm対応はかなり厳しいらしい https://github.com/apple/swift-nio/pull/1404#issuecomment-587357512 AsyncHTTPClientなどの基本的なHTTPクライアントが利用できない Firebase
Admin SDKのSwift版がないので、大変 用途はかなり限定されそう 37
まとめ 1. コード生成が気楽にできるようになる(SwiftTypeReader) ↓ 2. TypeScriptからでもSwiftの型が使えるようになる(CodableToTypeScript) 3. SwiftのprotocolでAPI定義できるようになる(CallableKit) ↓ 4.
ブラウザからSwift関数を呼べるようになる(CodableToTypeScript × CallableKit) 5. WasmからSwift関数を呼べるようになる(WasmCallableKit) Swiftがたくさん書けて嬉しい! 38
おわり 39