대형 데이터(이미지/동영상/바이너리)는 외부 저장: 파일로 저장하고 SwiftData에는 파일 URL/메타데이터만 보관.
무결성 제약: 식별자/고유 키가 필요하면 @Attribute(.unique) 등 제약을 적극적으로 사용(충돌 처리를 염두).
인덱싱 고려: 자주 필터/정렬하는 속성은 인덱스를 두어 조회 성능 개선(초기 설계 단계에서 결정).
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Model finalclassNote { @Attribute(.unique) var uuid: String var title: String var body: String var updatedAt: Date // 계산/뷰 전용 속성은 여기에 두지 않기! init(title: String, body: String) { self.uuid =UUID().uuidString self.title = title self.body = body self.updatedAt = .now } }
2) 관계(relationships) 설정
양방향 관계는 inverse를 정확히: 누락 시 동기화/삭제 규칙에서 예기치 못한 동작 가능.
삭제 규칙(Delete Rule): 보통 .cascade 또는 .nullify를 사용. 도메인 규칙에 맞게 선택.
To‑many 정렬은 보장하지 않음: 화면 표시용 정렬은 페치 시점에 명시적으로 정렬.
1 2 3 4 5 6 7 8 9 10 11 12
@Model finalclassFolder { var name: String @Relationship(deleteRule: .cascade, inverse: \Note.folder) var notes: [Note] = [] }
@Model finalclassNote { var title: String var folder: Folder? }
3) 컨테이너/컨텍스트 라이프사이클
****ModelContainer**는 앱 전체에서 공유, 화면 전환마다 새로 만들지 않기.
****ModelContext**는 스레드(액터) 경계에 안전하지 않음: 다른 Task/스레드로 전달하지 말고, 필요한 위치에서 새로 생성.
미리보기/테스트: isStoredInMemoryOnly: true로 인메모리 컨테이너를 사용해 빠른 반복.
1 2
let container =tryModelContainer(for: [Folder.self, Note.self]) let context =ModelContext(container)
4) 동시성 & 변경 추적
같은 모델 인스턴스를 여러 컨텍스트에서 공유하지 않기: 객체 정체성/스냅샷 충돌 위험. 필요 시 ID(예:**persistentModelID**)로 재조회.
대량 쓰기 작업은 배치로: 반복 저장 대신 일정 단위로 save() 호출하여 오버헤드/락 경합 완화.
UI 업데이트는 메인 액터에서: SwiftUI/@Query와의 일관성 유지.
1 2 3
try context.transaction { // 여러 삽입/수정/삭제를 묶어 원자적으로 처리 }
5) 페치(@Query / FetchDescriptor / #Predicate)
동적 필터는#Predicate 사용**: 문자열 쿼리 조립 불가. 키패스 기반으로 작성.
정렬/페이징 명시: sortBy, fetchLimit, fetchOffset을 활용해 과도한 로드 방지.
계산 속성/컬렉션 크기 등은 직접 조건에 사용할 수 없음: 저장 속성 기준으로 설계.
1 2 3 4 5 6
let descriptor =FetchDescriptor<Note>( predicate: #Predicate { $0.title.localizedStandardContains("swift") }, sortBy: [SortDescriptor(\.updatedAt, order: .reverse)], fetchLimit: 50 ) let results =try context.fetch(descriptor)