Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
わいわい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.8k
わいわいswiftc#19Genericsの特殊化
sidepelican
0
490
わいわい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
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
440
週末にAI-DLCを本気で回したら$1,600溶けた
hbashimizu
0
120
初心者DevRelとして参加者だった私が、DevRel Talks!#2に登壇するまでにしてきたこと
sokohirai
0
330
From 6 People Classroom Meetup to 100 People Regional Conference / FOSS4G Hiroshima 2026
furukawayasuto
0
100
XHTMLが残したもの
yosuke_furukawa
PRO
1
390
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
380
Hello, Hiroshima Geospatial Data! — Exploring DoboX with Python
ra0kley
0
170
まだ間に合う!今年の夏こそSchemeのマクロ展開器を完全理解!
omasanori
0
630
[PyCon KR 2026] More Variants, More Diversity for AI Accelerators
achimnol
0
130
承認済みなのに差戻しできてしまうバグ、型で潰せます
shinchit
0
140
業務時間外もAIに働いてもらう話
colorful12
3
9.9k
ソフトウェアラスタライザ
fadis
1
790
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1033
470k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
490
BBQ
matthewcrist
89
10k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.2k
Accessibility Awareness
sabderemane
1
200
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
320
Prompt Engineering for Job Search
mfonobong
0
440
A better future with KSS
kneath
240
18k
Producing Creativity
orderedlist
PRO
348
41k
Fireside Chat
paigeccino
42
4k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
460
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
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