🍏

WWDC2024から見る開発Tipsまとめ

2024/07/05に公開

ベースのセッション

https://developer.apple.com/videos/play/wwdc2024/10135/
https://developer.apple.com/videos/play/wwdc2024/10198/

コード補完

  • Swift用にトレーニングされたオンデバイスコーディングモデルを使用することでより強力なコード補完が実現
  • 周りのコードやコメントも参照する
  • Apple Silicon, MacOS Sequoia, Xcode16, UnifiedMemory16GBの環境で使用できる


使用している環境が対応しているかどうかはXcode16の設定>TextEditing>Editing>Predictive Code Completionでチェック可能

Swift6

Upcoming Features

  • Swift6では、同時実行の安全性を保証する新しい言語モードが追加される
  • これらを利用するには、Swift6言語モードを採用する必要がある
  • しかしXcode16ではそれぞれの今後の言語機能の警告を段階的に有効にすることが可能で、小さく始めることができる


Build Settings > Swift Compiler - Upcoming Featuresで今後の言語機能を一つずつ段階的に有効にできる

Xcode15以前はBuild Settings > Swift Compiler - Custom Flags > Other Swift Flagsに-enable-upcoming-feature ExistentialAnyなどを指定していたが、プルダウンから選択するだけでよくなっている

Swift Language Version


Swift6が追加された

Preview

Previewには記述がより簡単になり、再利用しやすく、モデルと統合しやすい2つの新しいAPIが追加

@Previewable

  • Bindingを引数にとるViewをPreviewで表示するために、従来はPreview専用のラッパーViewを作る必要があった
  • このラッパーに@Stateを保持させることでPreview上でBindingを操作していた
従来のラッパーView
struct SampleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle(isOn: $isOn) {
            Text("Toggle")
        }
    }
}

struct SampleViewPreviewWrapper: View {
    @State private var isOn = false

    var body: some View {
        SampleView(isOn: $isOn)
    }
}

// ラッパーを使用
#Preview {
    SampleViewPreviewWrapper()
}

// もしくは固定値を渡す
#Preview {
    SampleView(isOn: .constant(false))
}

今回追加された、@Previewableを使用することでこのラッパーを作成しなくてもPreviewで@Bindingを操作することが可能になった

@Previewableを使用した例
struct SampleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle(isOn: $isOn) {
            Text("Toggle")
        }
    }
}

#Preview {
    @Previewable @State var isOn = false

    SampleView(isOn: $isOn)
}

Preview Modifier

  • EnvironmentやPreview用のデータを簡単に共有できるようになる
  • データはPreviewシステムがデータをキャッシュできるようにもなる

少し説明を追加すると

  • PreviewModifierはPreview環境だけでViewに適用するModifier(のようなもの)
  • 従来はProcessInfoなどを見てPreview環境の場合は.environmentにPreview用のオブジェクトを注入する、といったことを時前でやっていた(筆者)
従来のPreview用オブジェクトを注入する方法
#Preview {
    HogeView()
        .environment(\.hogeObject, HogeObject())
}
  • この代替手段がPreviewModifier
  • .environment以外にもViewへの作用はなんでも書けそう
  • そして使用するデータはPreview環境がキャッシュしてくれる、という優れもの
  • ついでに補足するとPreviewマクロで適用できるPreviewTraitに、PreviewModifierを指定できる.modifierというstaticメソッドがiOS18で追加された

(PreviewTraitとか諸々は最低OSバージョンを上げないと使えないの、なんか理不尽ですよね、、)

PreviewTraitを使用したサンプル
struct SampleBookProvider: PreviewModifier {
    typealias Context = BookProvider

    static func makeSharedContext() async throws -> Context {
        // Preview専用のインスタンスなどをここで作成する
        // async, tryで書くことが可能、重い処理でもキャッシュされるので安心
        BookProvider()
    }

    func body(content: Content, context: Context) -> some View {
        // Preview環境で適用されるViewへの作用
        content.environment(context)
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    @MainActor static var sampleNamer: Self = .modifier(SampleBookProvider())
}

#Preview(traits: .sampleNamer) {
    RobotNameSelectorView()
}

PreviewModifierの実装手順

  • 1つ目はContextの指定
    • これはPreview環境で共有したいオブジェクト(=Context)の型を指定する
  • 2つ目はmakeSharedContext()の実装
    • このメソッド内で共有されるContext(sharedContext)を実際に作成する
    • そのため、メソッドはstaticになる
  • 3つ目はbody(content:context:)の実装
    • これはPreviewされるViewに対してどのような操作をするかを記述する
    • Previewごとに呼び出されるためこちらはstaticではない

Explicitly Built Modules

  • 新しいExplicitly Built Moduleにより、ビルドの並列性が向上し、診断が改善され、デバッグが高速化される(セッションではそう言っている)
  • Obj-CやC++ではデフォルトでこの機能がON
  • Swiftではオプトインになっているため、Build Settingsから自分でONにする必要がある


Build Settings > Swift Compiler - General > Explicitly Built Modulesで設定が可能

実際にどの程度ビルドが早くなるのか、どんなプロジェクトでも効果が期待できるのかなど、ここで話すにはコンテンツが大きすぎるため詳細は割愛します。詳しくは下記セッションを御覧ください。 
https://developer.apple.com/videos/play/wwdc2024/10171/

Disk write diagnostics

ディスク書き込みのdiagnosticsにも少し機能が追加


Xcodeのツールバー > Window > Organizerを選択、左側のタブからDisk Writesを選択


タイトルの先頭に優先度を示す矢印マークが追加された

Launch diagnostics

起動時間が長くなってしまってい部分、起動経路を検知することができる


Xcodeのツールバー > Window > Organizerを選択してOrganizerを表示、Launchesというカテゴリが追加されている

Unified Backtrace View

  • Stacktraceが見やすくなった
  • Backtrace in Editorを有効にした状態でブレイクポイントを止めると、それぞれのフレームの周辺コードが表示され、スクロールするだけでコールスタックをたどることができる


DebugBarのEnable Backtrace in Editor(6つ目のアイコン)で切り替えが可能


ホバーで変数の中身も見られる(ここは従来通り)

Swift Testing

Swift言語のための新しいTestフレームワークです。
@Testや#assertなどのマクロ、パラメタライズドテスト、テストケースへのラベル付け、より見やすいdiagnosticsなど使いやすい機能がたくさん入っています。

こちらもTipsとして話すには長くなってしまうため、詳細は下記のセッションを御覧ください。

https://developer.apple.com/videos/play/wwdc2024/10179/
https://developer.apple.com/videos/play/wwdc2024/10195/

Frame Graph

InstrumentsのCPU Profilerに追加された新しい機能で、実行中のフレームを見やすく可視化してくれます。例としてハングの原因を特定する手順を下記に記載します。

Hangするサンプルコード
struct HangView: View {
    var body: some View {
        Text("HangView")
            .task {
                saveFiles()
            }
    }

    private func saveFiles() {
        let line = Array(repeating: "a", count: 1000).joined(separator: "")
        let content = Array(repeating: line, count: 10000).joined(separator: "\n")
        for i in 0..<500 {
            saveFile(fileName: "file_\(i)", content: content)
        }
    }

    private func saveFile(fileName: String, content: String) {
        guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return
        }
        let fileURL = url.appendingPathComponent(fileName)
        do {
            try content.write(to: fileURL, atomically: true, encoding: .utf8)
            print("File saved successfully at \(fileURL)")
        } catch {
            print("Failed to save file: \(error.localizedDescription)")
        }
    }
}


ToolBar > Product > Profile(⌘I)


Instrumentsが起動するのでCPU Profilerを選択


レコードボタン(左上の赤丸)をクリックして記録開始


Hang(を含むCPUProfile)が記録される


Hangを右クリックしてSet Inspection Rangeを選択


Frame Graph(画面右下のちっちゃいアイコン)を選択


FrameGraphが表示される

Frame Graphは選択した範囲のうち、実行している割合が多い処理ほど左に表示され、そのCallStackが縦に並んでいく。


今回の例だと、Hangしている間の99.9%をContentView.saveFile(fileName:content:)が占めていることがわかる。

Crashlogs

  • Appleプラットフォームではどこでクラッシュしてもプログラムの状態を収集し、ログとして残す
  • LLDBはこのクラッシュログを使用してその時のデバッグセッションを再現することが可能


CrashLogを右クリックしてOpen withでXcodeを選択


対象のプロジェクトを選択してOpenをクリック


クラッシュ時のデバッグセッションが再現される

このとき、クラッシュしたアプリのバージョンがローカルでチェックアウトされていること、dSYMバンドルが利用可能になっている必要がある

Breakpoint

Xcode16からの新機能ではないが、紹介されていたので一応記載

  • ブレイクポイントを貼ると、デバッグ時にLLDBが行のブレイクポイントから実際に止まる処理(コードパス)を検出する
  • 例えばButtonではコンストラクタ、ラベル、タップ時のクロージャという3つのコードパスがある


1つのブレイクポイントに対して止まる箇所3つあることがBreakpoint Navigatorで確認できる


コードパスを個別に有効、無効を切り替えることも可能


LLDBのコマンドbreak listでブレイクポイントのコードパスを一覧で表示することも可能


LLDBのコマンドpで変数の中身を見られる

  • 他にもブレイクポイントに止まった際にLLDBコマンドを自動実行する機能、LLDBコマンドを実行した後に止めずに続けるオプションがある


コードパスを右クリックしてEdit Breakpointを選択、Add Actionを選択してDebugger Commandに任意のLLDBコマンドを設定、Automatically continue after evaluating actionsにチェックを付ければ止めずに実行できる

  • ブレイクポイントに差し掛かった際に任意の条件下でのみ止めたい場合はConditionを設定する


コードパスを右クリックしてEdit Breakpointを選択、Conditionに条件を入力

  • 上記のConditionを設定する場合、for文などで大量の繰り返し処理が走るとその都度LLDBが条件を評価する必要があり実行に時間がかかってしまう
  • そういった場合は直接Swiftに条件分岐のコードを書き、if文の中でブレイクポイントを設定するのが良い


また、ブレイクポイントを設定せずともraise(SIGSTOP)でコードを止めることができる

  • 一時的なブレイクポイントは、ブレイクポイントに差し掛かった際に一度だけ止まるブレイクポイント
  • 下記LLDBコマンドの項目でも触れているがtbreakコマンドで一時的なブレイクポイントを作成できる
  • 例えばAという箇所が実行された場合にのみBで止めたい場合、Aにブレイクポイントを貼ってDebugger Commandに"tbreak <Bの場所>"を設定するとこれを実現できる


コードパスを右クリックしてEdit Breakpointを選択、Add Actionを選択してDebugger Commandに"tbreak <任意の場所>"を入力

  • 指定された回数はブレイクポイントを止めずに無視することが可能


コードパスを右クリックしてEdit Breakpointを選択、ignoreに回数を設定

  • Xcode16でSwiftのErrorがどこかでthrowされた際に止まるブレイクポイントが新たに追加された


Breakpoint Navigatorの左下の+ボタンからSwift Error Breakpointを選択、あとはどこかでErrorがthrowされれば自動で止まってくれる

LLDBコマンド

ブレイクポイントの追加

b DetailView.swift:70

ブレイクポイントのリストを表示(ブレイクポイントのIDなどはここでチェック)

break list

ブレイクコマンドの追加
止めたくない場合はcontinueでDONEまでがセット
<LLDB command>は任意のLLDBコマンドで、例えばp "last video is (watchLater.last?.name)"など

break command add <breakpoint-id>
<LLDB command>
continue
DONE

ブレイクコマンドの削除

break command delete <breakpoint-id>

ヘルプ

help <command>
help <command> <option>

ヘルプの中を特定キーワードで検索(”Add LLDB commands”で検索するとbreak command addがヒットする)

aproprops <keyword>

ブレイクポイントに止まる条件を追加
<condition>は止める条件で、例えばビデオを読み込むFor文の中でビデオが60分以上だった場合のみ止めたい場合は"video.length > 60"など

break modify <breakpoint-id> --condition <condition>

一時的なブレイクポイントを貼る
(ブレイクポイントに差し掛かったときに一回のみ止まるブレイクポイント)

tbreak Importer.swift:67

指定された回数はブレイクポイントに差し掛かっても無視する

break modify <breakpoint-id> --ignore-count <ignore count(int)>
  • 変数の中身を見たり、Expressionを評価するコマンド
    • この類のコマンドとして他にもregister readやmemory readもあるが、Xcode15からは大体の場合はpコマンドで事足りる
    • ただし、プリミティブな型でない場合は引き続きpoなどを使用する必要がある
p <variable-name>

余談だが、pコマンドはdwim-printのエイリアス。dwimは"Do What I Mean"の略

@DebugDescription

実装そのままだとVariable Views(デバッグ中に左下のタブで見られる変数のインスペクタ)で配列の中身がパッと見られない(展開すれば見られる)


Variable Viewsだと配列はこんな表示になっている

Swift6では@DebugDescriptionマクロを使用してpコマンドの結果やVariable Viewsでの表示をカスタマイズすることができる

@DebugDescriptionの実装
@DebugDescription
struct WatchLaterItem {
    let video: Video
    let name: String
    let addedOn: Date

    var debugDescription: String {
        "\(name)-\(addedOn)"
    }
}


debugDescriptionが表示されている

既にCustomDebugStringConvertibleプロトコルを実装している場合、@DebugDescriptionマクロに置き換えることができるが、上述の通りdebugDescriptionにStoredPropertyとStringInterpolationを使用している場合に限る点に注意が必要

また、今までpoコマンドを使用していたケースでもpコマンドで代用可能になる

Discussion