🤔 문제 상황
가장 먼저 수정해야할 것은 모델이라고 느꼈다. 우선 모델이 중복되는 프로퍼티가 너무 많다고 느꼈고, 각 Type이나 Flavor, Color의 경우 저장하는 형식이 너무 비효율적이라고 생각했다.
Migration 문제
SwiftData를 사용한 앱이었기 때문에 custom Migration이 먼저 잘 되는지 테스트하기 위해서 코드를 작성했다.
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[SchemaV1.self, SchemaV2.self]
}
static var stages: [MigrationStage] {
[migrateV1toV2]
}
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: { context in
do {
let wineNotes = try context.fetch(FetchDescriptor<SchemaV1.WineNote>())
let coffeeNotes = try context.fetch(FetchDescriptor<SchemaV1.CoffeeNote>())
let whiskeyNotes = try context.fetch(FetchDescriptor<SchemaV1.WhiskeyNote>())
let cocktailNotes = try context.fetch(FetchDescriptor<SchemaV1.CocktailNote>())
migrate(from: wineNotes, by: context)
migrate(from: coffeeNotes, by: context)
migrate(from: whiskeyNotes, by: context)
migrate(from: cocktailNotes, by: context)
try context.save()
} catch {
print("Migration error: \(error)")
}
},
didMigrate: nil
)
}
enum SchemaV1: VersionedSchema {
static var models: [any PersistentModel.Type] {
[User.self, WineNote.self, CoffeeNote.self, WhiskeyNote.self, CocktailNote.self]
}
static var versionIdentifier = Schema.Version(1, 0, 0)
}
extension SchemaV1 {
@Model final class User { ... }
@Model final class WineNote { ... }
@Model final class CoffeeNote { ... }
@Model final class WhiskeyNote { ... }
@Model final class CocktailNote { ... }
}
enum SchemaV2: VersionedSchema {
static var models: [any PersistentModel.Type] {
[WineTastingNote.self, CoffeeTastingNote.self, WhiskeyTastingNote.self, CocktailTastingNote.self]
}
static var versionIdentifier = Schema.Version(2, 0, 0)
}
extension SchemaV2 {
protocol TastingNote { ... }
@Model final class WineTastingNote: TastingNote { ... }
@Model final class CoffeeTastingNote: TastingNote { ... }
@Model final class WhiskeyTastingNote: TastingNote { ... }
@Model final class CocktailTastingNote: TastingNote { ... }
struct Ingredient: Hashable, Codable { ... }
}
아래처럼 마이그레이션이 전혀 되지 않는 문제 발생...
아래처럼 SwiftTest를 활용하여 테스트 코드도 만들어 보았으나...
struct TasterTests {
@Test func example() async throws {
let url = FileManager.default.temporaryDirectory.appending(component: "default.store")
var container = try setupModelContainer(for: SchemaV1.self, url: url)
var context = ModelContext(container)
try loadSamleDataSchemaV1(context: context)
let wineNotes = try context.fetch(FetchDescriptor<SchemaV1.WineNote>())
container = try setupModelContainer(for: SchemaV2.self, url: url)
context = ModelContext(container)
let wineTastingNotes = try context.fetch(FetchDescriptor<SchemaV2.WineTastingNote>())
#expect(wineTastingNotes.first?.think == "think")
}
private func loadSamleDataSchemaV1(context: ModelContext) throws { ... }
}
마이그레이션 자체가 잘 안되는 듯 했다... 분명 빌드는 오류없이 잘 되는데...!
가장 큰 문제 중 하나는 SwiftData의 Data Migration에 대한 정보가 많이 없다는 점이었다. SwiftData의 Migration에는 lightweight와 custom이 있었는데 ligthweight의 경우 쉽게 가능했는데 custom으로 하는 경우 아래와 같이 breakpoint를 찍어 확인해보니 print는 잘 되었는데... context.insert 부분이 잘 안되는 듯 했다...
🤓 해결 과정
🎉 Migration 성공!
해결방법은 생각보다 간단했다... willMigrate, didMigrate를 간과했는데 나는 context가 남아있는줄 알고, willMigrate에서 저장까지 하면 된다고 생각했다. 하지만 willMigrate가 된 후 context가 리셋되고, didMigrate를 할때 context가 다시 생겨나게 된다는 것을 알았다. 따라서 willMigrate단계에서 새로운 데이터 구조로 매핑을 해주고 didMigrate에 insert해주면 되는 생각보다 간단한 문제였다...!
private static var wineTastingNotes: [SchemaV2.WineTastingNote] = []
private static var coffeeTastingNotes: [SchemaV2.CoffeeTastingNote] = []
private static var whiskeyTastingNotes: [SchemaV2.WhiskeyTastingNote] = []
private static var cocktailTastingNotes: [SchemaV2.CocktailTastingNote] = []
static let migrateV1toV2 = MigrationStage.custom(
fromVersion: SchemaV1.self,
toVersion: SchemaV2.self,
willMigrate: { context in
let wineNotes = try context.fetch(FetchDescriptor<SchemaV1.WineNote>())
let coffeeNotes = try context.fetch(FetchDescriptor<SchemaV1.CoffeeNote>())
let whiskeyNotes = try context.fetch(FetchDescriptor<SchemaV1.WhiskeyNote>())
let cocktailNotes = try context.fetch(FetchDescriptor<SchemaV1.CocktailNote>())
wineTastingNotes = wineNotes.map { ... }
coffeeTastingNotes = coffeeNotes.map { ... }
whiskeyTastingNotes = whiskeyNotes.map { ... }
cocktailTastingNotes = cocktailNotes.map { ... }
}, didMigrate: { context in
wineTastingNotes.forEach { context.insert($0) }
coffeeTastingNotes.forEach { context.insert($0) }
whiskeyTastingNotes.forEach { context.insert($0) }
cocktailTastingNotes.forEach { context.insert($0) }
try context.save()
}
)
우선 문제해결을 위해서 코드를 막(?) 작성했는데 조금더 디테일한 변경을 위한 수정이 필요하다. 다음 게시글에 모델을 어떻게 변경하는지에 대한 구체적인 과정을 담을 예정...!
우선 몇시간동안 붙잡고 있었던 문제가 해결되니 너무 좋다. 😇 솔직히 SwiftData를 포기하고 다른 저장방식을 사용할까도 고민했는데 잘 해결되었다...! 끝까지 붙잡고 있어서 다행이다.
'→ Taster' 카테고리의 다른 글
[Project-Taster] 모델 수정 (SwiftData) (3) | 2024.11.01 |
---|---|
[Project-Taster] 기본 컴포넌트 적용 및 아키텍처 변경 (0) | 2024.10.13 |
[Project-Taster] 앱을 고도화 시켜보자!! (1) | 2024.10.06 |
[Project-Taster] 버전 업 - 폰트 적용 및 중복 이미지 코드화 (0) | 2024.08.13 |
[Project-Taster] 버전 업 - 시작 (0) | 2024.08.12 |