はじめまして!5月24日に入社した、YOUTRUST のくまもん(YOUTRUST/Twitter)です。アプリエンジニアをやっています。
この記事では、Appleが毎年6月ごろに開催しているイベント WWDC の Platforms State of the Union を見たので、個人的に気になった部分(主にマクロの導入)について書きたいと思います。*1
マクロの導入
Swiftにマクロが導入されることにより、より使いやすく簡単に、ボイラープレートの少ないコードを書くことができると説明されています。
たとえば @CaseDetection
マクロは、ある enum のインスタンスが特定の case かどうか判定する computed property を生成します。
@CaseDetection enum Topic { case diversity case mentorship // @CaseDetection マクロをつけたことによって、 // 以下を書かなくても自動的に以下のcomputed propertyが使えるようになる var isDiversity: Bool { if case .diversity = self { true } else { false } } var isMentorship: Bool { if case mentorship = self { true } else { false } } }
Freestanding macros
上記の例は enum に @XXX
という形でアノテートするマクロでしたが、 もうひとつ、#
から始まる形式で記述するマクロ (Freestanding macros) もあります。
#assert(x == y - 1)
たとえば、 #URL("")
マクロ は、コンパイル時にURLが妥当なものかチェックしてくれるようです(従来のように unwrap したりしなくてOK)。たとえばURLに半角スペースを入れることはできないが、以下はエラーになります。autocorrectでスペースを自動的に削除する機能も提供されるようです。
#URL(https://www.swift.org/ diversity/") # Invalid URL: spaces not allowed in URLs
Attached macros
@
から始まるマクロ (Attached macros) では、すでに書いたコードに新しい機能を追加することができます。
たとえば、以下のようにコールバック関数を使った非同期関数の実装を行っているとします。
struct SwiftWebsite { func fetchContent(_ topic: Topic, completion: @escaping (Result<String, Error>) -> Void) { let task = URLSession.shared.dataTask(with: topic.url) { ... } task.resume() } } let website = SwiftWebsite() let task = Task { var content: String? = "" website.fetchContent(.diversity) { result in if case .success(let result) = result { content = result } } if let content { print(content) } } await task.value
これを Swift Concurrencyの async/await に対応させるために、マクロを使うことができるそうです。関数に @AddAsync
を追加することで、使う側はシンプルに await
で結果を受け取ることができます。
struct SwiftWebsite { @AddAsync func fetchContent(_ topic: Topic, completion: @escaping (Result<String, Error>) -> Void) { let task = URLSession.shared.dataTask(with: topic.url) { ... } task.resume() } } let website = SwiftWebsite() let task = Task { let content = try? await website.fetchContent(.diversity) if let content { print(content) } } await task.value
Xcodeとの統合
Xcode でマクロ部分のコードを右クリックして Expand Macros メニューを選ぶと、マクロによってどのようなコードが自動生成されたかがわかるそうです。デバッガでブレークポイントを張ってマクロの行に到達したときに、Step Into を選ぶことでマイクロの内容を展開して表示することもできるようです。
そのほかのマクロ
Platforms State of the Union では詳細は説明されていなかったですが、Swift はオープンソースで開発されているため、すでに開発者はマクロを使用して様々な機能を開発していると紹介されていました。スライド上で紹介されていたマクロは以下の通り。
#assert @CustomCodable @DictionaryStorage @AddCompletionHandler @MemberwiseInit @OptionSet #JSONModel @Bitfield @NewType
マクロによってシンプルになったSwiftUIのデータバインディング
SwiftUIでアプリを作るとき、Viewでモデルの更新を反映するためのプロパティラッパーには、いくつかの選択肢がありました。
@State @StateObject @ObservedObject @Environment @EnvironmentObject
iOS 17からのデータ管理はよりシンプルになって、
@State @Environment
だけに集中できるようになると説明しています。
具体的には、 SwiftUIにモデルを公開するときに、ObservableObjectに準拠し、各プロパティに @Published
プロパティーラッパーを追加するような以下のようなコードを書いたことがあるかもしれませんが、
class Bird: ObservableObject { @Published var name: String @Published var species: Species @Published var songs: [Song] }
これが、マクロが追加されたことによって、 モデルを @Observable
でアノテートすることで以下のように書けるとされています。
@Observable class Bird { var name: String var species: Species var songs: [Song] }
これだけで、SwiftUIのViewで@ObservedObject
などのプロパティラッパーを付与しなくても、モデルの更新を View に反映できるそうです。
struct BirdDetail: View { var bird: Bird // @ObservedObject はつけなくていい var body: some View { VStack { Text(bird.name) // 自動で更新される SpeciesDetail(bird.species) // 自動で更新される } // bird.songs は View で使われていないため、 append してもViewの更新は起こらない } }
このとき、@Observable
はViewで使用されているプロパティが変更されたときにのみ、Viewを更新するため、 View で使用されていないプロパティを変更したときに、無駄に View が再描画されるようなことはないそうです。
マクロによって書くのが簡単になった Preview
今までSwiftUI の プレビューを書くには、以下のようなコードを記述していました。
struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
これからは、 #Preview
マクロを使って、以下のように簡潔に記述することができるようです。
#Preview {
Button("SwiftUI")
}
これだけでもめでたそうですが、なんと同じノリでUIKitやAppKitのプレビューも書けてしまうようです。
#Preview { UIButton("UIKit") } #Preview { NSButton("AppKit") }
ほか気になったトピック
Platforms State of the Union では他にも、SwiftUIのアニメーションのアップデートや、iOS17 で iPhone を横置きにしたときに現れる新しいロック画面 StandBy や macOS Sonoma の Widget の表示のアップデートを踏まえた WidgetKit の話、新機能などをユーザーに知らせるための新しいフレームワーク TipKit、 ゲームの macOS 対応の話、ScreenCaptureKit のアップデート、StoreKit のアップデート、などなど様々なトピックが紹介されていましたが、特に以下のトピックは興味深かったです。
- SwiftData
- Sensitive Content Analysis フレームワーク
- わずかなコードで、センシティブな画像や動画などをオンデバイスで検出できる
let result = try! await SCSensitivityAnalyzer().analyze(at: imageURL)
のような形- もし
result.isSensitive
が true だったら imageURL はセンシティブ
- もし
- わずかなコードで、センシティブな画像や動画などをオンデバイスで検出できる
- 自動(UI)テスト
- Xcode で左ペインにテストのタイムライン、右側に録画を並べて表示できる
- タイムラインを使って、任意の瞬間にジャンプすることができる
- テストがViewをスクロールした時や、検索フィールドをタップした時など、興味深いポイントにはマークが表示される
- visionOS関連
- デザインや奥行きの表現
- Windows, Volumes, Spaces という概念
- つまんで操作するといった、これまでとは違う新しい操作方法
- visionOS 上のUIも SwiftUI を使って構築することができる。たとえば
.offset(z:
viewモディファイアを使って、UI要素に微妙な奥行きの変化を加えることができる
- デザインや奥行きの表現
個人的にチェックしたいセッション
セッションのリストを見て、今のところ時間があれば個人的にチェックしたいなと思っているセッションは以下になります。
- Swift/SwiftUI/Swift macros について
- SwiftData について
- 自動(UI)テストなどについて
- visionOS 関連
- 趣味
おわりに
YOUTRUST では、日々の業務時間内で着手しづらい開発に取り組む時間として、月に1回「KAIZEN Day」が設けられています。この日は通常業務をストップし、納期や優先順位を気にすることなく、エンジニア自身の判断で開発・改善できる日です。
「それを行うことで、サービスが良くなるかどうか」という基準をクリアしていればどんなことでもOKとのことで、入社したての自分は、もっと直接的に改善に寄与できることを考えてやったほうがいいかも…?と思いつつも、やや興味優先で、 WWDC のキャッチアップをさせていただきました。
結局YOUTRUSTはアプリの開発にFlutterを利用しているというのもあり、今回の内容だとすぐに直接活かせそうな部分は思いつきませんでしたが(ないんかい)、とはいえ、FlutterもiOSの上で動いているので、キャッチアップしておくことで、何か機能開発や、自動テストの体験改善などのアイデアに繋がることもあるかもしれないなと思いました。今後 visionOS に対応することもあるかもしれないし(?)
というわけで、こんな内容でいいのかな…と思いつつ、まとめた内容を初参加のKAIZEN Dayの終わりに内容を共有したのですが、実際に共有したら思った以上に興味を持ってもらえて、テックブログに書きませんかという話になったので、懐が広くて助かる!!という感じでした(普通にいい感じの改善をやっている人もいれば、シンプルに技術の勉強してる人とかも居たので、結構自由な印象でした)。今後はより改善っぽいことにも挑戦したいなと思います!