Slide 1

Slide 1 text

Swift Macroでメソッドの実行時間を 計測できるようにしてみた CP本部 iOSアプリ開発部 中山 龍 2024年10月25日 mobile.stmn #8

Slide 2

Slide 2 text

自己紹介 中山 龍 (なかやま りゅう) ● 株式会社kubell ○ 新卒2年目のiOSエンジニア(22) ○ 「Chatwork」アプリの開発を担当 ● 愛知県在住 ○ 日曜に引っ越しをし、熱田区民 → 中区民に @ryu_develop

Slide 3

Slide 3 text

今更ながらSwift Macro (Swift Syntax) で遊んでみたのでその内容を紹介します

Slide 4

Slide 4 text

注意事項 ● 本スライドの内容や登場するコードは趣味で作成しているものであり、業 務で扱っているプログラム・プロダクト・開発環境とは一切関係ありません ● 「試しに触ってみた」という趣旨の紹介であり、必ずしもベストプラクティス を紹介しているわけではありません ● スライド・GitHubに掲載しているコードも完璧な動作を保証するものでは ありません

Slide 5

Slide 5 text

01 | Swift Macroとは

Slide 6

Slide 6 text

Swift Macroとは? - Swiftプログラム内でコンパイル時にコードを自動生成したり、変換したりする ための機能 - Swift5.9以降で導入 - @〜〜〜 や #〜〜〜といった感じで使用する 参考: Swiftマクロの書き方 - WWDC2023 代表的なものだと - Observation (@Observable) - SwiftUIのプレビュー (#Preview) 強力で便利な機能だが、可読性などの観点から乱用しないように注意が必要!

Slide 7

Slide 7 text

02 | 実行時間を計測するMacroを書いてみた

Slide 8

Slide 8 text

完成イメージ 8 @(自作のMacro) 型に対して自作のMacroを付ける 型のメソッドを実行した時に、 実行したメソッド と時間 が表示され る

Slide 9

Slide 9 text

コードが見づらい部分も多いと思うので... https://github.com/Ryu-nakay/measuri ng-method-execution-time-macro.git コードを中心に説明をすると字が小さくて見づらいことが 想定されるので、文字中心で説明します〜!🙏 コードは必要に応じてGitHubをご覧ください → (スライドはイベント終了後に公開するので、その際に照ら し合わせながら見ていただくでも問題なし) 会場の様子を見ながら、必要そうであればライブコーディング風にお見せします

Slide 10

Slide 10 text

1. 単体のメソッドを計測可能にする

Slide 11

Slide 11 text

ルール: メソッドの実行時間の計測 メソッドの 処理開始時の時刻 と 処理終了時の時刻 の差を実行時間とする 11 終了時刻 開始時刻 実行時間 10時 20分 40秒 10時 20分 30秒 10秒 ざっくりとしたイメージ

Slide 12

Slide 12 text

ルール: メソッドの実行時間の計測 メソッドの 処理開始時の時刻 と 処理終了時の時刻 の差を実行時間とする 12 終了時刻 開始時刻 実行時間 10時 20分 40秒 10時 20分 30秒 10秒 ざっくりとしたイメージ メソッドの開始時刻と終了時刻を取得し、その差を 出力すればよさそう!

Slide 13

Slide 13 text

ルール: メソッドの実行時間の計測 -作成するMacroのイメージ- 13 展開 @自作Macro @自作Macro (今回、defer文などは考慮していません )

Slide 14

Slide 14 text

ルール: メソッドの実行時間の計測 -計測に使用する便利な型- 14 計測の事前準備として、MeasureTimeLoggerという型を作りました - 初期化時に時刻でプロパティを初期化 - stop呼び出しで実行時間の計算とprint出力

Slide 15

Slide 15 text

ルール: メソッドの実行時間の計測 -計測に使用する便利な型- 15 展開 BodyMacro を使ってメソッドの中身を書き換えよう!

Slide 16

Slide 16 text

2. BodyMacroを実装する

Slide 17

Slide 17 text

実装 -型の準備- BodyMacroに準拠した型を定義し、expansionメソッドを準備します // 実装

Slide 18

Slide 18 text

実装 -expansionメソッドの中身- 1. Macroが付与されている対象がメソッド構文であることを確認し、そのメソッドの中身の構文を取得する (できなければ何もイジらずに return) 2. メソッドの中身の構文を 1行ずつ取り出し、その行が return構文だった場合には、その前に `measureTimeLogger.stop()` をくっつける return構文が存在していなかった場合には、メソッドの中身の構文の末尾に `measureTimeLogger.stop()`をくっつ ける 3. メソッドの中身の先頭に `let measureTimeLogger = MeasureTimeLogger()`をくっつける

Slide 19

Slide 19 text

実装 -expansionメソッドの中身- 1. Macroが付与されている対象がメソッド構文であることを確認し、そのメソッドの中身の構文を取得する (できなければ何もイジらずに return) 2. メソッドの中身の構文を 1行ずつ取り出し、その行が return構文だった場合には、その前に `measureTimeLogger.stop()` をくっつける return構文が存在していなかった場合には、メソッドの中身の構文の末尾に `measureTimeLogger.stop()`をくっつ ける 3. メソッドの中身の先頭に `let measureTimeLogger = MeasureTimeLogger()`をくっつける 1. Macroが付与されている対象がメソッド構文であることを確認し、そのメソッドの中身の構文を取得する (できなければ何もイジらずに return) - expansionメソッドの引数 declaration が FunctionDeclSyntax型に変換できるかで判定する - FunctionDeclSyntax.body?.statements がメソッドの中身の構文となる

Slide 20

Slide 20 text

実装 -expansionメソッドの中身- 1. Macroが付与されている対象がメソッド構文であることを確認し、そのメソッドの中身の構文を取得する (できなければ何もイジらずに return) 2. メソッドの中身の構文を 1行ずつ取り出し、その行が return構文だった場合には、その前に `measureTimeLogger.stop()` をくっつける return構文が存在していなかった場合には、メソッドの中身の構文の末尾に `measureTimeLogger.stop()`をくっつ ける 3. メソッドの中身の先頭に `let measureTimeLogger = MeasureTimeLogger()`をくっつける - 1.で取得した『メソッドの中身の構文』を 1行ずつ取り出し、その型が ReturnStmtSyntaxかどうか でreturn構文を判定することができる - 元の構文 + 処理終了時の構文となったものが出来上がる 2. メソッドの中身の構文を 1行ずつ取り出し、その行が return構文だった場合には、その前に `measureTimeLogger.stop()` を付け加える return構文が存在していなかった場合には、メソッドの中身の構文の末尾に `measureTimeLogger.stop()`を付け加 える

Slide 21

Slide 21 text

実装 -expansionメソッドの中身- 1. Macroが付与されている対象がメソッド構文であることを確認し、そのメソッドの中身の構文を取得する (できなければ何もイジらずに return) 2. メソッドの中身の構文を 1行ずつ取り出し、その行が return構文だった場合には、その前に `measureTimeLogger.stop()` をくっつける return構文が存在していなかった場合には、メソッドの中身の構文の末尾に `measureTimeLogger.stop()`をくっつ ける 3. メソッドの中身の先頭に `let measureTimeLogger = MeasureTimeLogger()`をくっつける 3. メソッドの中身の先頭に `let measureTimeLogger = MeasureTimeLogger()`を付け加える - 2.で作成した『元のメソッドの構文 + メソッド終了時の構文』の先頭にメソッド開始時の構文を付け 加えてreturnする

Slide 22

Slide 22 text

最終的なBodyMacroの実装 https://github.com/Ryu-nakay/measuri ng-method-execution-time-macro.git

Slide 23

Slide 23 text

実装 あとは、Macroを使えるようにするために各所に記述を追加し ...

Slide 24

Slide 24 text

実装 無事に時間計測ができるようになりました!

Slide 25

Slide 25 text

Macro展開時 ↑ Macroを展開すると計測開始・終了の処理が付け加えられ ていることがわかります

Slide 26

Slide 26 text

一括でつけたい 無事に計測可能になったものの... 毎回メソッドに付与していくの面倒だなぁ... 一括で型のメソッド全てを計測可能にできないかな?

Slide 27

Slide 27 text

再掲: 完成イメージ 27 @(自作のMacro) 型に対して自作のMacroを付ける 型のメソッドを実行した時に、 実行したメソッド と時間 が表示され る

Slide 28

Slide 28 text

こうすればいける? 28 @(自作のMacro) Macroを展開 メソッド単体での計測はできるので、型に対してMacroをつけた時に右のように各 メソッドに @MeasureTimeが付与されるようにすればいいのでは?

Slide 29

Slide 29 text

3. MemberAttributeMacroを実装する

Slide 30

Slide 30 text

実装 -最終的なMemberAttributeMacroのコード- さっきと比べるとすごくシンプル!

Slide 31

Slide 31 text

実装 -最終的なMemberAttributeMacroのコード- expansionを実装 memberがメソッドの 構文であるかを判定 メソッドの構文であるものに対して @MeasureTimeを付与

Slide 32

Slide 32 text

実装 -最終的なMemberAttributeMacroのコード- expansionを実装 memberがメソッドの 構文であるかを判定 メソッドの構文であるものに対して @MeasureTimeを付与 これだけで完成!

Slide 33

Slide 33 text

実装 あとは、Macroを使えるようにするために各所に記述を追加し ...

Slide 34

Slide 34 text

使ってみる 34 Macroを展開 型に @TimeMeasureable を付けることで、その型が持つメソッドに @MeasureTimeを付与できました!

Slide 35

Slide 35 text

まとめ

Slide 36

Slide 36 text

まとめ・感想 36 まとめ ● メソッド単体で計測可能することができる @MeasureTime を実装した ○ BodyMacro ● 型のメソッドに @MeasureTimeを付与できる @TimeMeasureableを実装した ○ MemberAttributeMacro 感想 ● 「Swift Macro / Swift Syntaxって専門知識がないと難しそう」と食わず嫌いしていたが、実際に 触ってみると動くものを作れるぐらいにはできた ● Swift AST Explorer が便利すぎた ○ Syntaxの深い知識を持ち合わせていなくても構造や型を調べられた ● 本当は「Swiftのコードから実行された処理のシーケンス図を出力したい」というモチベーションで 始めたので、今後実現できたらと ...

Slide 37

Slide 37 text

働くをもっと楽しく、創造的に 37