SwiftData 기본 사용법 Model, ModelContext, Query
엔티티 정의
@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/
- externalStorage 실제 경로 ⇒ 앱의 Documents 디렉토리 내 별도 폴더
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() // 모든 변경사항 한번에 저장
}
이러게 변경 사항들을 보관하고있다가 일괄 저장 한다 함