「この計測、どこで送ってる?」をなくす

-- Views

September 18, 26

スライド概要

extension DC 2026 Day1 発表資料

profile-image

koshimizu takehito / iOS app engineer

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

「この計測、どこで送ってる?」をなくす Swift Macros による宣⾔的トラッキング extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

2.

この計測どこで送ってる? 記述に⾃由があると、実は誤った実装をしてしまう 2 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

3.

書ける場所が多すぎる 計測処理をどこに書いても、コンパイルはできる func purchase() { // ① 先頭 guard let item = selectedItem else { return } // ② guard の後 presenter.purchase(item) // ③ 末尾 } 3 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

4.

誤った実装1: ⽋測する 必ず呼びたいので末尾に実装した。 guard が追加されると計測ができなくなる。 func purchase() { guard let item = selectedItem else { return } presenter.purchase(item) Analytics.track(event: "purchase_requested") } 4 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

5.

誤った実装2: 過剰に出る 処理の前処理として計測がしたいので先頭に実装した。 guard が追加されると、処理が実⾏されなくても計測されてしまう。 func purchase() { Analytics.track(event: "purchase_completed") guard let item = selectedItem else { return } guard item.isAvailable else { return } presenter.purchase(item) } 5 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

6.
[beta]
誤った実装3: 失敗が成功になる

エラーの場合だけ計測したい。
defer を追加すると throw で抜けても計測してしまう。

func purchase(_ item: Item) throws -> Receipt {
defer { Analytics.track(event: "purchase_completed") }
return try charge(item)
}

6

extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

7.

こう書きたい 送るタイミングを when で宣⾔する。 Macroが期待するタイミングで処理を展開する。 @Tracked(event: "purchase_completed", when: .onSuccess) func purchase() { guard let item = selectedItem else { return } guard item.isAvailable else { return } presenter.purchase(item) } 7 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

8.
[beta]
作ったマクロ

@attached(body)
macro Tracked(event: String, when: When)

// 関数の中⾝を書き換えるマクロ
// When は4つの case
// - .before
// - .after
// - .onSuccess
// - .onFailure

@Tracked(event: "purchase_completed", when: .onSuccess)
func purchase(_ item: Item) -> Receipt { ... }

8

extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

9.

9 Quiz extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

10.

Quiz 指定⽅法で、実装がどう差し替わるか予想してみてください。 • .before: 前処理 • .after: 後処理 • .onSuccess: 処理が成功した場合 • .onFailure: 処理が失敗した場合 10 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

11.

Quiz 1 11 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

12.

.before メソッドが呼ばれた瞬間に記録したい。 @Tracked(event: "view_home", when: .before) func showHome() { presenter.loadHome() } 12 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

13.

.before 先頭に計測処理を挿⼊ func showHome() { Analytics.track(event: "view_home") presenter.loadHome() } 13 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

14.

Quiz 2 14 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

15.

.after 途中で return しても、抜けるときに必ず記録したい。 @Tracked(event: "purchase_requested", when: .after) func purchase() { guard let item = selectedItem else { return } presenter.purchase(item) } 15 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

16.

.after defer で囲む。スコープを出るときに必ず⾛る。 func purchase() { defer { Analytics.track(event: "purchase_requested") } guard let item = selectedItem else { return } presenter.purchase(item) } 16 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

17.

Quiz 3 17 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

18.
[beta]
.onSuccess

最後まで到達したときだけ記録したい。

@Tracked(event: "purchase_completed", when: .onSuccess)
func purchase(_ item: Item) -> Receipt? {
guard item.isAvailable else { return nil }
let receipt = charge(item)
return receipt
}

18 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

19.
[beta]
.onSuccess

フラグを⽴てて、defer の中で判定する。

func purchase(_ item: Item) -> Receipt? {
var didComplete = false
defer {
if didComplete {
Analytics.track(event: "purchase_completed")
}
}
guard item.isAvailable else { return nil }
let receipt = charge(item)
didComplete = true
return receipt
}
19 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

20.

Quiz 4 20 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

21.
[beta]
.onFailure

throw したときだけ記録したい。

@Tracked(event: "purchase_failed", when: .onFailure)
func purchase(_ item: Item) throws -> Receipt {
return try charge(item)
}

21 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

22.
[beta]
.onFailure

do-catch で包み、記録してから再 throw する。

func purchase(_ item: Item) throws -> Receipt {
do {
return try charge(item)
} catch {
Analytics.track(event: "purchase_failed")
throw error
}
}
22 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

23.

運⽤して出た課題 実際に運⽤してみて困ったこと。 23 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

24.
[beta]
同じメソッドに⼆度付けできない
同じメソッドにマクロを2つ付けたくなる。
ただし body マクロは1関数に1つまで(SE-0415)。

@Recorded(id: 1024, when: .after)
@Tracked(event: "purchase_requested", when: .after)
func purchase() {
startPurchase()
}
24 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

25.

同じメソッドに⼆度付けできない コンパイルエラーにはならない。後ろの1つだけが展開される。 // 展開されたのは、後ろだけ func purchase() { defer { Analytics.track(event: "purchase_requested") } startPurchase() } 25 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

26.

まとめ • 宣⾔的な記述によって、誤った実装を潰すことができる • 展開後の実装がイメージできていればマクロの実装はできる • body マクロの⼆度付けは後ろの1つだけが展開される 26 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_

27.

ありがとうございました 27 extension DC 2026 Day1 / ⼩清⽔健⼈ @_take_hito_