🙌

SwiftDataをSwiftUIから分離する

2024/04/21に公開

きっかけ

SwiftDataをSwiftUIから分離したかった。
SwiftUIとセットの運用する記事はたくさん出てきたけど、分離する情報はあんまり無かったので、調べたことをここにまとめておきます

やりたいこと

  • 今まで使っていたローカルDBをRealmからSwiftDataに移行したい。
  • 使用する環境はPresenter経由で呼び出す(Viewから直接呼び出さない)

Modelクラスを準備する

この辺はどの方法でも変わらない。
まず保存したいクラスに @Model マクロをつけてSwiftDataで扱えるモデルとして定義する

ContentData.swift
@Model
class ContentData {
    let contentId: String
    let imageData: Data
    var text: String
    
    init(
        contentId: String,
        imageData: Data,
        text: String
    ) {
        self.contentId = contentId
        self.imageData = imageData
        self.text = text
    }
}

I/Oクラス

SwiftDataをimportし、ModelContainerプロパティを生成する

ContentsRepository.swift
import SwiftData

@MainActor
struct ContentsRepository {
    var container: ModelContainer?
    
    init() {
        self.container = try? ModelContainer(for: ContentData.self)
    }

    ...
}

CRUD処理

Create

ContentsRepository.swift
func append(with content: ContentData) async {
    container?.mainContext.insert(content)
}

READ

ContentsRepository.swift
func fetchAll() async -> [ContentData] {
    var result = [ContentData]()
    let descriptor = FetchDescriptor<ContentData>(sortBy: [SortDescriptor<ContentData>()])
    do {
        result = try container?.mainContext.fetch(descriptor) ?? []
    } catch {
        print(error)
    }
    return result
}

UPDATE

ContentsRepository.swift
func update(content: ContentData, with text: String) async {
    let descriptor = FetchDescriptor<ContentData>(sortBy: [SortDescriptor<ContentData>()])
    let list = (try? container?.mainContext.fetch(descriptor)) ?? []
    let target = list.first(where: { $0.contentId == content.contentId })
    target?.text = text
}

DELETE

ContentsRepository.swift
func delete(content: ContentData) async {
    let descriptor = FetchDescriptor<ContentData>(sortBy: [SortDescriptor<ContentData>()])
    let list = (try? container?.mainContext.fetch(descriptor)) ?? []
    guard let target = list.first(where: { $0.contentId == content.contentId }) else { return }
    container?.mainContext.delete(target)
}

まとめ

とりあえず、これだけでI/Oできるようになります。
今回のコードはGistでこちらにまとめておきました。

ModelContainerの生成のところに気をつければ特に問題なくスムーズに扱うことができますが、Descriptorから対象のデータを抽出する方法はもっと良い方法が有る気がします🤨

Discussion