Rails 7のalphaが出たのをきっかけにHotwireを触っている。 (Hotwireが出た当時は動画などを見るだけで触っていなかった)
Turbo NativeのiOS版が意外と(?)ちゃんと動いており興味を持ったので、 少しだけ中身を見てみた。
準備
サーバー側はrails newして適当にページを作っておく。 (Railsじゃなくてもいいけど慣れてるし楽なので)
1
2
3
4
$ rails -v
Rails 7.0.0.alpha2
$ rails new myapp -j esbuild
-j esbuild
がない(JSをbundleせずにImport mapsを使う)とiOSシミュレータで動かなかったので付けた。
クライアント(iOS)を動かすための最低限のコードはこちらにある
https://github.com/hotwired/turbo-ios/blob/main/Docs/QuickStartGuide.md
これで画面遷移がNavigationControllerをつかったものになる。
何が起きているのか?
雑にコードを眺めてみる。
SessionDelegate
1
2
3
4
5
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
window!.rootViewController = navigationController
visit(url: URL(string: "http://localhost:3000")!)
}
visitを呼ぶ。
1
2
3
4
5
6
private func visit(url: URL) {
let viewController = VisitableViewController(url: url)
navigationController.pushViewController(viewController, animated: true)
session.visit(viewController)
}
sessionのvisit呼ぶ。
Session
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public func visit(_ visitable: Visitable, action: VisitAction) {
visit(visitable, options: VisitOptions(action: action, response: nil))
}
public func visit(_ visitable: Visitable, options: VisitOptions? = nil, reload: Bool = false) {
// 省略
let visit = makeVisit(for: visitable, options: options ?? VisitOptions())
currentVisit?.cancel()
currentVisit = visit
visit.delegate = self
visit.start()
}
private func makeVisit(for visitable: Visitable, options: VisitOptions) -> Visit {
if initialized {
return JavaScriptVisit(visitable: visitable, options: options, bridge: bridge, restorationIdentifier: restorationIdentifier(for: visitable))
} else {
return ColdBootVisit(visitable: visitable, options: options, bridge: bridge)
}
}
なんやかんやで JavaScriptVisitのstartVisitが呼ばれる
JavaScriptVisit
1
2
3
4
5
override func startVisit() {
debugLog(self)
bridge.visitDelegate = self
bridge.visitLocation(location, options: options, restorationIdentifier: restorationIdentifier)
}
bridgeのvisitLocationに続く。
WebViewBridge
1
2
3
4
5
6
7
func visitLocation(_ location: URL, options: VisitOptions, restorationIdentifier: String?) {
callJavaScript(function: "window.turboNative.visitLocationWithOptionsAndRestorationIdentifier", arguments: [
location.absoluteString,
options.toJSON(),
restorationIdentifier
])
}
JSの世界へ。
turbo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
visitLocationWithOptionsAndRestorationIdentifier(location, options, restorationIdentifier) {
if (window.Turbo) {
Turbo.navigator.startVisit(location, restorationIdentifier, options)
} else if (window.Turbolinks) {
if (Turbolinks.controller.startVisitToLocationWithAction) {
// Turbolinks 5
Turbolinks.controller.startVisitToLocationWithAction(location, options.action, restorationIdentifier)
} else {
// Turbolinks 5.3
Turbolinks.controller.startVisitToLocation(location, restorationIdentifier, options)
}
}
}
これでTurboにより初回ページが読み込みされる。
↓でregisterAdapterされてるので、ページ内にあるリンクも同様にturbo-ios内のturbo.jsによって処理される。
turbo.js
1
2
3
4
5
6
7
8
9
registerAdapter() {
if (window.Turbo) {
Turbo.registerAdapter(this)
} else if (window.Turbolinks) {
Turbolinks.controller.adapter = this
} else {
this.pageLoadFailed()
}
}
なんとなくわかった(?)のでバグに遭遇しても対処できそうな気がしてきた。