![見出し画像](https://assets.st-note.com/production/uploads/images/119201705/rectangle_large_type_2_7fd0050149a6a69dc1187481a428b7fc.jpeg?width=1200)
マクロ と オブザベーション
SwiftData を支えている Swift のマクロとオブザベーションを確認しましょう。
SwiftData はとても少ないコーディングで魔法のように動作します。
その要因がマクロとオブザベーションです。
執筆時点の環境:
Xcode 15
macOS 13.6 と macOS 14.0
この記事は『SwiftData を iOS アプリでためす』マガジンで読むことができます。
『SwiftData を iOS アプリでためす』マガジンで読める記事:
SwiftDataをシンプルにためす
CSVファイルデータを読みSwiftDataで使う
アプリ起動時の表示情報にSwiftDataを使う
マクロ と オブザベーション(この記事)
SwiftDataの検索・絞り込みと並べ替え
iOS18に追加されたSwiftDataの新機能 【2024年10月18日追加】
画像クリックで拡大表示できます
画像を拡大表示中は画像の左右をクリックで画像だけを順に表示できます
ソースコード部分は左右にスクロールできます
リンクしているドキュメントは英文が多いですが、翻訳機能を活用してください
SwiftDataを支えるもの
SwiftData がマクロを使って(依存して)いることはWWDC2023の「Meet SwiftData」(日本語タイトル:「SwiftDataについて」)セッションの冒頭で述べられています。
SwiftDataはシームレスな API体験のために Swift language macrosによる 表現性に頼ります
Xcode 15 でソースコードの import SwiftData から Jump to Definition すると Observation フレームワークをインポートしているのを確認できます。
![](https://assets.st-note.com/img/1696920204700-ASnsUTfO1t.jpg?width=1200)
マクロ(Macros)
マクロはソースコードの記述で古くから使われてきた機能です。
Swift 登場前 Objective-C言語でも使っていました。
Objective-C のマクロは C言語のマクロそのままで簡単に定義できましたが、単純にコードを文字列として置き換えるだけのため注意点や限界がありました。
マクロは SwiftData をはじめ後半で解説する Observation でも使われていて、WWDC2023でも次のセッションで解説されています(日本語字幕付き)。
「What's new in Swift」(「Swiftの新機能」) Swift Macroのチャプターあり
「Expand on Swift macros」(「Swiftマクロの拡張」)
「Write Swift macros」(「Swiftマクロの書き方」)
マクロで言語自体の可能性を広げられ ボイラープレートを取り除き Swiftの表現豊かなパワーを引き出します
マクロはSwift 5.9の新機能
Swiftのマクロは Swift 5.9 の新機能です。
Swift 5.9 は Xcode 15 から利用可能な Swift の最新バージョンです。
Swiftの機能なのでマクロのドキュメントは swift.org のサイトにあります。
上記日本語版から冒頭部分を引用します:
繰り返しコードを生成するために、コンパイル時にコードを変換します。
マクロは、ソースコードのコンパイル時にコードを変換し、あなたが繰り返し同じコードを手で書く手間を省いてくれます。コンパイルの間、Swift は、通常通りのコードの構築をする前にマクロを展開します。
マクロを展開することは、常に加算操作です。つまり、マクロは新しいコードを追加しますが、既存のコードを削除したり修正したりすることはありません。
マクロへの入力とマクロ展開の出力の両方が、構文的に有効な Swift コードであることを確認するためにチェックされます。同様に、マクロに渡す値とマクロによって生成されるコードの値は、それらが正しい型であるかを確認するためにチェックされます。さらに、マクロの実装がそのマクロを展開するときにエラーが発生した場合、コンパイラはこれをコンパイルエラーとして扱います。これらの保証により、マクロを使用するコードの推論が容易になり、マクロの使い方が間違っている、マクロの実装にバグがある、などの問題を特定しやすくなります。
このようにSwiftのマクロは単なる文字列の置き換えでは得られない高度な展開が可能で、適切なエラー表示もあり安心して利用できます。
#Previewなど「#」で始まる自立型マクロ(Freestanding Macros)と @Model など「@」で始まる付属型マクロ(Attached Macrosin page link)の2種類があります。
#も@もこれまでのSwift文法で使われていますが、マクロか従来の使い方かは意識せず利用可能です。
ここではマクロの作成には触れません。
Xcode のマクロ展開表示
Xcode 15 でマクロを展開表示できます。
ここではSwiftUIテンプレートの #Preview マクロで試します。
![](https://assets.st-note.com/img/1697069519175-aiZm7tLGMd.jpg?width=1200)
マクロ名(#PreviewのPreview)を選び Editor > Expand Macro メニュー、またはコンテキストメニューの Expand Macro メニュー項目を使います。
![](https://assets.st-note.com/img/1697069437325-9G59edhVje.jpg?width=1200)
このメニュー項目はエディターで選択している部分が展開できない場合は選択できません。
![](https://assets.st-note.com/img/1697069445053-plkfoPVuSQ.jpg?width=1200)
コンテキストメニューではエディターで選択している部分が展開できない場合は Expand Macro メニュー項目を表示しません。
Expand Macro するとソースコード上に表示します。
#Preview マクロの下記の例では12行展開されました。
![](https://assets.st-note.com/img/1697069635011-l4zXnRaZIm.jpg?width=1200)
マクロを展開すると独立した行番号を表示します。
展開表示は複数のブロックにわかれている場合があります。
マクロを展開すると Editor メニューの Expand Macro メニュー項目は「Hide Macro Expansion」に切り替わり「Hide All Macro Expansions」メニュー項目が選択可能になります。
![](https://assets.st-note.com/img/1697530320964-nsc2ICTaP5.jpg?width=1200)
各ブロックの行番号部分の ✖︎ ボタンで展開表示をブロック単位で閉じることができます。
Editor > Hide All Macro Expansions メニューで展開表示されたすべてのブロックを閉じることができます。
展開した状態でブレークポイントを設定もできます。
展開してもコードに影響はありませんし、展開したすべてのブロックを閉じるといつでも元のコードに戻ります。
マクロ展開部分の操作
マクロを展開した部分は選択しコピーもできます。
選択はブロック単位です、ブロックをまたいで連続選択はできません。
ブロック内の単語を選択した後に ⌘A するとブロック内全体を選択します。
展開部分は選択はできますが入力や編集はできません。
Jump to Definition や Show Quick Help もできません。
Fold 操作もできません。
編集できないので Refactor もできません。
マクロ展開表示部分は表示されていますが、検索の対象ではありません。
展開部分にマクロがある場合、そのマクロは展開表示可能です。(展開できない場合もありました)
マクロ展開部分にあるマクロ
SwiftData の @Model マクロを展開して表示される @_PersistedProperty もマクロで展開表示もできます。
どのマクロの展開結果かはブロックの右側に表示されます。
![](https://assets.st-note.com/img/1697532247319-AiBYzIMFhj.jpg?width=1200)
@_PersistedProperty の場合は二つのブロックに展開表示されます。
ブロックは続けて表示されています。
展開された行の行番号はそれぞれ独立しているので 2 が二つ表示されています。
Xcode 15 では二重に展開表示した状態では Editor > Hide All Macro Expansions メニューで展開したブロックを閉じない場合がありました。
二重に展開した状態で元コードのマクロに対してEditor > Hide Macro Expansion を選ぶと全てを閉じ元のコード表示にもどります。
Xcode 15 では @Model マクロを展開して表示される @Transient は Expand Macro メニュー項目が選択可能ですが、ビープ音がして展開表示されません。
SwiftUI関連のマクロ
SwiftUIフレームワークページを「macro」でフィルタリングすると Observable() と Preview(_:body:) やPreview(:traits::body:) などが列挙されます。
SwiftDataフレームワークページではModel()、Attribute(_:originalName:hashModifier:)、Transient()、Query()、ModelActor()、Relationship(_:deleteRule:minimumModelCount:maximumModelCount:originalName:inverse:hashModifier:) が列挙されました。(Queryは引数違いが複数あります)
SwiftDataフレームワークのマクロ多用が際立ちます。
一部のマクロは Xcode 15 で展開できないものがあります。
マクロの効果
SwiftUI の @Model マクロを展開し内包するマクロも展開してみました。
元のコード
![](https://assets.st-note.com/img/1697592016467-5TgldhjEmf.jpg?width=1200)
展開したコード
![](https://assets.st-note.com/img/1697592027606-2cz24vDleY.jpg?width=1200)
かなりの行数がマクロで追加されていることが確認できます。
SwiftDataを使うためには展開後のコードの手入力が必要であれば実用的ではないことは明らかです。
マクロのドキュメントにあるように『マクロは、(省略)あなたが繰り返し同じコードを手で書く手間を省いてくれます。』の効果は絶大でバグの混入を防ぎ開発の効率を上げます。
マクロの表記について
ドキュメントで個々のマクロは Observable() や Model() など関数の形式で書かれています。
一方コードでは @Observable や @Model などカッコなしで記述します。
引数を持つマクロもあるので検索などはカッコを含めるのがよさそうです。
解説文内では Observable() より @Observable とコード上の書き方が個人的にはしっくりきます、このため表記の混在はご容赦ください。
たとえば Observable() マクロは
Observable()
@Observable
Observableマクロ
などと表記している場合があります(すべて「Observable() マクロ」のことです)。
Observationフレームワーク
Observationはモデルの変化を監視し変化した場合はそれを表示しているビューを更新する機能を受け持つフレームワークです。
WWDCの短いビデオがあります。
Discover Observation in SwiftUI(SwiftUIにおけるObservationの説明)
Observationの特徴
Observation の特徴はパフォーマンスです。
SwiftUI の updateページにも明記されています。
Get better performance when you share data throughout your app by using the new Observable() macro.
新しいObservable()マクロを使用して、アプリ全体でデータを共有すると、パフォーマンスが向上します。【macOSの翻訳機能を利用】
しかしWWDC2023の複数のセッションではあまり具体的な説明はありません。
この点は後半で触れます。
今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。