개발/Swift

SwiftData 기본 사용법 Model, ModelContext, Query

덤벨로퍼 2025. 5. 8. 09:03

엔티티 정의

@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 매크로 써서 외부 저장소에 저장가능

  • 좀 느리지만 큰 데이터 다루는 용도
  • 일반적인 내부데이터는 SQLite DB 사용하지만 이것은 Document 폴더에 들어간다고 함
    • externalStorage 실제 경로 ⇒ 앱의 Documents 디렉토리 내 별도 폴더
      • ~/Library/Application Support/[앱 번들 ID].store/external_storage/
    • 일반 데이터 (ex> name, email ) ⇒ 앱의 Application Support 디렉토리에 저장됨
      • ~/Library/Application Support/[앱번들ID].store/

context

CRUD 작업을 가능하게 하는 객체

@Environment(\\.modelContext) private var context

일반적으로 view에서는 위와 같이 매크로를 통해 쉽게 접근 가능함

근데 초기에 model container 세팅해줘야함

WindowGroup {
    ContentView()
}
.modelContainer(for: User.self) // SwiftData의 저장소 시스템을 초기화하고 활성화

데이터 타입이 여러개라면 schema에 넣어주면됨,

그외 버전관리, 옵션 config 세팅도 가능함

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 private var users: [User] 
private var user: User? {
  users.first
}

위와같이 단일 데이터만 필요한 경우에도 배열로 쿼리가가능

  • Reducer 에서는 불러올수 없었음, 이외 ViewModel이나 Repository 파일 같이 분리된 파일도 마찬가지임

→ SwiftUI.View 에서만 동작함 , modelContext 도 마찬가지로 View에서만 불러올수 있음

Accessing Environment<ModelContext>'s value outside of being installed on a View. This will always read the default value and will not update.

디자인 패턴 or 아키텍쳐 적용시 ModelContext 를 VM이나 RP에 전달하여 사용할수있겠지만 (or Reducer)

이러 저러한 문제가 발생할수 있으며 Query 매크로 자체를 못쓰기 때문에

Query 매크로 등등 이점을 활용하기가 어려워짐, 따라서 View에서 그냥 접근하는게 낫다고 판단이 됨.

정렬

    @Query(sort: \\Keyword.date, order: .reverse) private var keywords: [Keyword]

날짜 오름차순 정렬인경우 위와같이 하면됨

\.date 키패스로 접근하려했지만 Cannot infer key path type from context; consider explicitly specifying a root type 이런 에러가 발생하여 명시적으로 타입을 넣어줌

 

실시간 데이터

Query로 불러온 데이터가 저장을 통해 새로운 데이터가 들어왔을떄 API나 coredata 같은 경우는 refresh를 통해 새로운 값을 다시 불러와야했다.

@Query private var users: [User] 

근데 쿼리를 사용하면 알아서 실시간 데이터를 대응해줌

“ObservableObject와 Publisher를 사용하여, 데이터베이스의 변경을 감지하면 뷰를 다시 그리도록 트리거합니다” 라고 함

 

데이터 수정

@Query private var users: [User] 
private var user: User? {
  users.first
}
@Environment(\\.modelContext) private var context

위와같이 간편하게 context 가져옴

func editUser() {
	user?.name = "james"
	try? context.save()
}

user.name 을 변경하면 메모리 차원에서 변경이 적용됨, (ex>페이지 떠있을떄)

context.save() 하는 순간 영구 저장소에 저장 (커밋) 됨

아주 쉽고 간편하게 변경이 됨

파라미터를 던져주는것도 아닌데 어떻게 변경된것을 알고 저장할까?

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()        // 모든 변경사항 한번에 저장
}

이러게 변경 사항들을 보관하고있다가 일괄 저장 한다 함