ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SwiftData 기본 사용법 Model, ModelContext, Query
    개발/Swift 2025. 5. 8. 09:03
    이번 글에서는 SwiftData의 핵심 요소인 Model, ModelContext, Query의 기본 사용법을 자세히 다룹니다. SwiftData를 활용하여 효율적인 데이터 관리를 시작해 보세요!
    --------------------------------------------------------------------------------

     

    엔티티 정의 (Model)

     

    SwiftData에서 데이터를 저장하기 위해서는 먼저 엔티티를 정의해야 합니다. @Model 매크로를 사용하여 쉽게 데이터 모델을 생성할 수 있습니다. Identifiable 프로토콜을 채택하는 것이 일반적입니다.
    @Model final class User: Identifiable {
        var name: String
        var email: String
        @Attribute(.externalStorage) var imageData: Data?
    
        init(name: String, email: String, imageData: Data? = nil) {
            self.name = name
            self.email = email
            self.imageData = imageData
        }
    }

     

    여기서 주목할 점은 @Attribute(.externalStorage) 매크로입니다.
    이 매크로를 사용하면 큰 데이터를 외부 저장소에 저장할 수 있습니다.
    • @Attribute(.externalStorage)를 사용한 데이터 (예: imageData)는 앱의 Documents 디렉토리 내 별도 폴더에 저장됩니다. 실제 경로는 ~/Library/Application Support/[앱 번들 ID].store/external_storage/와 같습니다.
    • 이는 일반적인 내부 데이터 저장 방식(SQLite DB)보다 조금 느릴 수 있지만, 대용량 데이터를 다루는 데 유용합니다.
    • 일반적인 내부 데이터 (예: name, email)는 SQLite DB를 사용하며, 앱의 Application Support 디렉토리에 저장됩니다. 실제 경로는 ~/Library/Application Support/[앱번들ID].store/와 같습니다.

     

    ModelContext: 데이터 CRUD의 핵심

    ModelContext는 SwiftData에서 데이터 생성(Create), 읽기(Read), 업데이트(Update), 삭제(Delete) 작업을 가능하게 하는 객체입니다.
    View에서는 @Environment(\\.modelContext) 매크로를 통해 쉽게 접근할 수 있습니다.
    @Environment(\\.modelContext) private var context
    
    하지만 ModelContext를 사용하기 전에 초기에 ModelContainer를 설정해 주어야 합니다.
    WindowGroup.modelContainer() 모디파이어를 사용하여 SwiftData의 저장소 시스템을 초기화하고 활성화할 수 있습니다.
    WindowGroup {
        ContentView()
    }
    .modelContainer(for: User.self) // SwiftData의 저장소 시스템을 초기화하고 활성화
    
    만약 다루어야 할 데이터 타입이 여러 개라면 Schema에 추가하여 설정할 수 있으며, 이 외에도 **버전 관리, 다양한 옵션 및 설정(Configuration)**이 가능합니다.
    다음은 여러 타입을 포함하는 ModelContainer 설정 예시입니다.
    private var modelContainer: ModelContainer = {
        let schema = Schema([User.self]) //여기 다른 타입 추가
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
        do {
               return try ModelContainer(for: schema, configurations: [modelConfiguration])
           } catch {
               fatalError("Could not create ModelContainer: \\(error)")
           }
    }()
    
    WindowGroup {
            MyPageView(store: Store(initialState: .init(), reducer: { AppCoordinator()}))
        }
        .modelContainer(modelContainer)
    
    

     

     

     

    Query: 데이터를 효율적으로 가져오기

    @Query 매크로는 SwiftData에서 데이터를 간편하게 가져오는 데 사용됩니다. 필요한 데이터를 배열 형태로 쿼리할 수 있습니다.
    @Query private var users: [User]
    private var user: User? { users.first }
    
    위 코드처럼 단일 데이터만 필요한 경우에도 배열로 쿼리가 가능합니다.
    Query와 ModelContext 사용 시 주의사항:
    @Query 매크로와 ModelContext는 현재 SwiftUI.View 내에서만 직접적으로 동작합니다.
    즉, Reducer, ViewModel, Repository 파일 등 분리된 파일에서는 직접적으로 불러올 수 없습니다.
    만약 View 외부에서 ModelContext에 접근하려고 하면 다음과 같은 오류 메시지를 볼 수 있습니다.
    Accessing Environment<ModelContext>'s value outside of being installed on a View. This will always read the default value and will not update.
    
    물론 디자인 패턴이나 아키텍처를 적용하여 ModelContext를 ViewModel이나 Repository, Reducer 등에 전달하여 사용할 수는 있습니다.
    하지만 이 경우 @Query 매크로 자체를 사용할 수 없게 되어 Query 매크로가 제공하는 실시간 데이터 반응성과 같은 이점을 활용하기 어려워집니다.
    따라서 View에서 직접 접근하는 것이 더 효율적이라고 판단됩니다.
     
     

    데이터 정렬 (Sorting)

    @Query 매크드를 사용할 때 sort 파라미터를 통해 데이터를 쉽게 정렬할 수 있습니다.
    @Query(sort: \Keyword.date, order: .reverse) private var keywords: [Keyword]
    
    위 예시처럼 @Querysort 파라미터를 추가하여 Keyword 모델의 date 속성을 기준으로 내림차순(reverse)으로 정렬할 수 있습니다. 만약 날짜 오름차순 정렬이 필요한 경우에도 이와 같은 방식으로 설정하면 됩니다.
    참고로, \.date와 같이 키패스로 접근하려 할 때 Cannot infer key path type from context; consider explicitly specifying a root type와 같은 에러가 발생할 수 있습니다.
    이 경우 위 예시처럼 \Keyword.date와 같이 명시적으로 타입을 지정해 주면 문제를 해결할 수 있습니다.
     
     

    실시간 데이터 (Real-time Data)

    @Query의 가장 큰 장점 중 하나는 실시간 데이터 대응 능력입니다.
    기존 API나 Core Data와 같은 방식에서는 데이터가 저장되거나 변경되었을 때 refresh를 통해 새로운 값을 다시 불러와야 했습니다.
    @Query private var users: [User]
    
    하지만 @Query를 사용하면 별도의 새로고침 과정 없이 데이터가 변경되면 알아서 실시간으로 뷰에 반영됩니다.
    이는 SwiftData가 "ObservableObject와 Publisher를 사용하여, 데이터베이스의 변경을 감지하면 뷰를 다시 그리도록 트리거" 하기 때문입니다.
    이 덕분에 개발자는 데이터 동기화에 대한 복잡한 처리를 신경 쓸 필요가 없습니다.
     
     

     

    데이터 수정 (Updating Data)

    SwiftData에서 데이터를 수정하는 과정은 매우 간편합니다. ModelContext를 사용하여 쉽게 변경 사항을 커밋할 수 있습니다.
    @Query private var users: [User]
    private var user: User? { users.first }
    @Environment(\\.modelContext) private var context
    
    func editUser() {
        user?.name = "james"
        try? context.save()
    }
    
    위 코드처럼 context를 간편하게 가져와 사용할 수 있습니다.
    user?.name = "james"와 같이 모델 객체의 속성을 변경하면 메모리 차원에서 즉시 변경이 적용됩니다 (예: 현재 떠 있는 페이지에 변경 사항 반영).
    이후 context.save()를 호출하는 순간, 변경된 데이터가 **영구 저장소에 저장(커밋)**됩니다.
    SwiftData는 어떻게 변경된 데이터를 알고 저장할까요?
    SwiftData의 ModelContext는 내부적으로 변경 사항을 추적하는 메커니즘을 가지고 있습니다.
    class ModelContext {
        // 내부적으로 이런 식의 변경사항 추적
        private var changedObjects: Set<PersistentModel>
        private var insertedObjects: Set<PersistentModel>
        private var deletedObjects: Set<PersistentModel>
    }
    
    // 사용 예시
    @Environment(\\.modelContext) private var context
    
    func example() {
        let user = User(name: "Kim")
        context.insert(user) // insertedObjects에 추가
    
        user.name = "Park" // changedObjects에 추가
    
        context.delete(user) // deletedObjects에 추가
    
        try? context.save() // 모든 변경사항 한 번에 저장
    }
    
    이처럼 ModelContext는 **changedObjects, insertedObjects, deletedObjects**와 같은 내부 집합을 통해 변경된 객체들을 보관하고 있다가 context.save()가 호출될 때 모든 변경 사항을 한 번에 일괄 저장합니다.
    이 덕분에 개발자는 복잡한 데이터 관리 없이 쉽고 간편하게 데이터를 변경하고 저장할 수 있습니다.
     

     

    댓글

Designed by Tistory.