코드네임 :
🍏 SwiftData와 데이터베이스 본문
와 졸려
[ SwiftData ]
@Model
- SwiftData에서 데이터 모델을 정의할 때 사용하는 속석 래퍼입니다.
- 클래스에 붙여서 데이터베이스 테이블처럼 사용할 수 있도록 지정합니다.
- @Model은 반드시 참조 타입(Class)에만 사용할 수 있습니다.
@Model
class Task {
var title: String
var isDone: Bool
var createdAt: Date
init(title: String, isDone: Bool = false, createdAt: Date = .now) {
self.title = title
self.isDone = isDone
self.createdAt = createdAt
}
}
@Attribute
- 특정 속성에 대한 세부 설정을 할 때 사용합니다.
- 인코딩 방식, 기본값, 제약 조건 등을 지정할 수 있
@Attribute(.unique) var email: String
@Attribute(.externalStorage) var photoData: Data?
- @Attribute(.unique)
- 고유한 값만 허용한다는 의미입니다.
- 위 코드로 설명을 하면, 같은 값을 가진 email을 가진 두 개의 객체는 데이터베이스에 동시에 존재할 수 없습니다.
- 보통 회원가입 기능을 구현할 때, 이메일이나 사용자 ID는 중복되면 안되겠죠?!
- @Attribute(.externalStorage)
- 파일처럼 덩치가 큰 데이터를 데이터베이스 내부가 아닌 외부에 저장하겠다는 의미입니다.
- SwiftData는 내부적으로 이 속성을 가진 데이터를 앱의 샌드박스 파일 시스템에 따로 저장합니다.
- 이미지, 동영상, 오디오 등 Data 타입으로 저장되는 큰 바이너리 파일을 그대로 SQLite에 저장하면 앱 성능에 나쁜 영향을 줄 수 있어요.
@Relationship
- 모델 간 관계를 설정할 때 사용합니다.
- 명시하지 않아도 배열을 통해 관계가 자동 인식되긴 하지만, 복잡한 관계를 다룰 때 명시적으로 사용하면 좋습니다.
- 필요한 것만 선택해서 설명합니다.
@Relationship(deleteRule: .cascade) var tasks: [Task] // deleteRule 속성은 아래 4개입니다.
- .cascad
- 부모가 삭제되면, 연결된 자식도 자동으로 함께 삭제
- .nullify
- 부모가 삭제되면, 자식 객체의 관계만 끊고 객체 자체는 남겨둠
- .deny
- 부모 객체가 자식과 연결된 상태이면 삭제 자체를 막음
- .noAction
- 부모가 삭제되더라도, 자식 객체는 아무 변화 없이 남겨짐
- 부모가 삭제되더라도, 자식 객체는 아무 변화 없이 남겨짐
@Relationship(.unique) var profile: Profile?
- .unique
- 1 : 1 관계를 나타냄 즉, 하나의 자식 객체가 여러 부모에게 동시에 속할 수 없게 만드는 옵션
- 만약 1 : N 관계를 나타내고 싶으면 참조 타입을 배열로 감싸면 됨. 즉, [Profile]? 이렇게 감싸면 1 : N 관계가 됩니
@Model
class User {
var name: String
@Relationship(inverse: \Book.owner) var books: [Book]
}
@Model
class Book {
var title: String
@Relationship(inverse: \User.books) var owner: User
}
- invers
- 두 모델의 관계 속성이 서로 연결되어 있다는 것을 명시합니다. 즉, 양방향 참조 관계!!
- 양방향으로 연결된 관계를 명확하게 알려줍니다.
@Transient
- 영속적으로 저장되지 않는 속성을 만들 때 사용합니다.
- 주로 계산된 값(computed-like 저장 값)이나 UI용 속성 등 일시적 데이터를 처리할 때 사용됩니다.
@Transient var tempID: UUID = UUID()
@ModelContext
- 저장소 컨텍스트를 SwiftUI 뷰 또는 ViewModel에서 사용하기 위한 환경 속성입니다.
- @Model 내부에서는 직접 사용하지 않지만, SwiftData 전체에서 필수 역할이라 함께 이해하면 좋습니다.
@Query
@Query는 SwiftData에서 데이터를 읽어오는(read) 데에 사용하는 아주 핵심적인 속성
[ 읽기 기본 사용법 ]
<전체 조회>
@Query var tasks: [Task]
값이 새롭게 업데이트 되면 뷰 또한 최신 상태로 자동 업데이트 됩니다.
대신 조건이 있습니다. 무조건 View 안에 선언해야 합니다. 뷰모델 선언 아닙니다
<조건 필터링 조회>
@Query(filter: #Predicate<Task> { $0.isDone == false })
var pendingTasks: [Task]
<정렬 적용>
@Query(sort: \Task.createdAt, order: .reverse)
var recentTasks: [Task]
@Query는 읽기 전용 입니다.
데이터를 쓰거나 수정하려면 @Environment(\.modelContext)로 ModelContext를 주입받아야 합니다.
[ 쓰기 기본 사용법 ]
<새로운 데이터 삽입>
let newTask = Task(title: "새 할 일")
context.insert(newTask) // 이 부분 중요
try? context.save() // 이 부분 중요
<데이터 수정>
task.isDone.toggle()
try? context.save()
<데이터 삭제>
context.delete(task)
try? context.save()
⬇️ Todo 만들기
<DataModel.swift>
@Model
class Task {
@Attribute(.unique) var title: String
var isDone: Bool
var createdAt: Date
@Transient var isBeingEdited: Bool = false // 현재 편집 중인지 나타내는 임시 상태 변수 생성
init(title: String, isDone: Bool = false, createdAt: Date = .now) {
self.title = title
self.isDone = isDone
self.createdAt = createdAt
}
}
<SwiftDataPracticeApp.swift> (메인 앱 구조)
import SwiftUI
import SwiftData
@main
struct SwiftDataPracticeApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: Task.self) // 나는 Task라는 모델을 저장하고 불러올거야!! 그러니까 이 모델을 위한 저장소를 준비해줘!! 라고 말하는 거에요! 중요합니다!! 꼭 작성해야 돼요!!
}
}
<ContentView>
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var context
@Query(sort: \Task.createdAt, order: .reverse) private var tasks: [Task]
@State private var newTaskTitle: String = ""
var body: some View {
NavigationStack {
VStack {
HStack {
TextField("할 일 입력", text: $newTaskTitle)
.textFieldStyle(.roundedBorder)
Button("추가") {
addTask()
}
.disabled(newTaskTitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
}
List {
ForEach(tasks) { task in
HStack {
Button {
toggleDone(task)
} label: {
Image(systemName: task.isDone ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isDone ? .green : .gray)
.imageScale(.large)
}
VStack(alignment: .leading) {
Text(task.title)
.strikethrough(task.isDone)
Text(task.createdAt.formatted(date: .numeric, time: .shortened))
.font(.caption)
.foregroundStyle(.gray)
}
}
}
.onDelete(perform: deleteTask)
}
}
.navigationTitle("할 일 목록")
}
}
private func addTask() {
let trimmed = newTaskTitle.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
let task = Task(title: trimmed)
context.insert(task)
try? context.save()
newTaskTitle = ""
}
private func toggleDone(_ task: Task) {
task.isDone.toggle()
try? context.save()
}
private func deleteTask(at offsets: IndexSet) {
for index in offsets {
context.delete(tasks[index])
}
try? context.save()
}
}
#Preview {
ContentView()
}
'👥Club > 🍀UMC🍀' 카테고리의 다른 글
🍎 DispatchQueue (0) | 2025.04.08 |
---|---|
🍎 SOLID 원칙 (0) | 2025.04.08 |
🍎 4주차 워크북 - OCR (이후 비번 설정할 예정) (0) | 2025.04.08 |
🍎 4주차 문법 - 함수와 클로저, 클래스, 구조체 (0) | 2025.04.06 |
🍏 이미지 관리 (0) | 2025.04.02 |