Upgrade to Pro — share decks privately, control downloads, hide ads and more …

@Queryマクロに頼らないSwiftDataの使い方

Avatar for ionishi321 ionishi321
November 28, 2025

 @Queryマクロに頼らないSwiftDataの使い方

Avatar for ionishi321

ionishi321

November 28, 2025

Other Decks in Technology

Transcript

  1. 自己紹介 struct Profile { let name = "大西 伊吹(おいぶ@ionishi321)" let

    company = "akippa株式会社(iOSエンジニア)" let hometown = "愛媛" let hobbies = ["水泳", "個人開発"] } 1
  2. SwiftDataを使ってみよう! まずはModelを定義 @Model // マクロで簡単にモデル化 final class TodoModel { @Attribute(.unique)

    var id: UUID var content: String init(id: UUID, content: String) { self.id = id self.content = content } } 5
  3. SwiftDataを使ってみよう! 親ViewにmodelContainerを渡しましょう! 各Viewから@Environment(\.modelContext) で取得できま す! @main struct SampleApp: App {

    var body: some Scene { WindowGroup { ListWithMacroView() } .modelContainer(for: TodoModel.self) // modelContainerを設定 } } 7
  4. SwiftDataを使ってみよう! ModelContainer経由でinsertとdeleteを準備しよう struct ListWithMacroView: View { @Environment(\.modelContext) private var modelContext

    // 追加 @Query var todo: [TodoModel] var body: some View { … } private func insertTodo(todoModel: TodoModel) { … } // 追加 private func deleteTodo(todoModel: TodoModel) { … } // 追加 } 8
  5. これらのアイディアを取り入れたアーキテクチャ MainActor View2 UI の構築 ViewModel2 @Observable 表⽰状態の整形 Store @Observable

    各ViewModel へのデータの公開・同期 Repository CRUD 処理 Entity View1 UI の構築 ViewModel1 @Observable 表⽰状態の整形 購読 購読 購読 Background Actor SwiftDataClient ModelActor SwiftData 操作 Model / Entity の変換 SwiftData 永続化 Model 購読 Entity 15
  6. SwiftDataClient ModelActorに準拠し、SwiftDataへのCRUD処理を全て担う アクター境界を超えるため、入出力にはSendableなEntityを利用する actor SwiftDataClient: ModelActor { … private init(modelContainer:

    ModelContainer) { … } // CRUD処理を行うメソッド // 入出力にはSendableなEntityを使い、内部でModelに変換して扱う func get<T: EntityConvertible>(id: UUID, from modelType: T.Type) -> T.Entity? { … } func fetch<T: EntityConvertible>(descriptor: FetchDescriptor<T>) -> [T.Entity] { … } func insert<T: EntityConvertible>(entity: T.Entity, from modelType: T.Type) { … } func delete<T: EntityConvertible>(entity: T.Entity, from modelType: T.Type) { … } } 16
  7. SwiftDataClient テスト用に、 inMemory なSwiftDataを扱うインスタンスを返せるようにすること で、依存するRepositoryのテストを書きやすくする extension SwiftDataClient { // 本番環境用のインスタンス

    static let liveValue: SwiftDataClient = .init(modelContainer: modelContainer(forTest: false)) // テスト用のinMemoryなインスタンス static var testValue: SwiftDataClient { .init(modelContainer: modelContainer(forTest: true)) } private static func modelContainer(forTest: Bool) -> ModelContainer { … } } 17
  8. Entity Sendable, Codable, Identifiable, Hashableに準拠した構造体 SwiftDataのModelごとに対応したEntityを作成する protocol EntityProtocol: Sendable, Codable,

    Hashable, Identifiable { var id: UUID { get } } struct TodoEntity: EntityProtocol { var id: UUID var content: String } 18
  9. Store Observableなクラスとして実装し、SSOTとして読み出した永続化データの公開を 責務とする @MainActor @Observable final class TodoStore: TodoStoreProtocol {

    private(set) var todos = [TodoEntity]() // Entity公開用プロパティ @ObservationIgnored var todoRepository: any TodoRepositoryProtocol init(todoRepository: any TodoRepositoryProtocol) { self.todoRepository = todoRepository Task { await syncTodos() } } func syncTodos() async { … } // todosを最新の状態と同期するメソッド func insertTodo(entity: TodoEntity) async { … } func deleteTodo(id: UUID) async { … } } 20
  10. ViewModel TodoStoreが公開するObservableなプロパティを購読し、Viewに表示すべき状態に 整える @MainActor @Observable final class ListWithoutMacroViewModel { let

    todoStore: any TodoStoreProtocol init(todoStore: any TodoStoreProtocol) { self.todoStore = todoStore } var todos: [TodoEntity] { todoStore.todos } // Storeで公開されたプロパティを参照 func insertTodo(todoEntity: TodoEntity) async { … } func deleteTodo(id: UUID) async { … } } 21
  11. View @Query マクロを含む状態とロジックをViewModelに分離したことで、UIを構築す る責務のみに整理される struct ListWithoutMacroView: View { @State private

    var viewModel: ListWithoutMacroViewModel init(viewModel: ListWithoutMacroViewModel) { self.viewModel = viewModel } var body: some View { ForEach(viewModel.todos, id: \.self) { todo in … } // ViewModel.todosを使ってUIを作成 } } 22