1. 마이그레이션
보통 이전 소프트웨어 -> 최신 소프트웨어로 업데이트되는 것을 통용해서 마이그레이션이라고 한다.
이와 같은 경우에 마이그레이션을 진행해야한다.
- 새로운 테이블을 만들 경우
- 기존테이블 -> 컬림 삭제 / 추가 / 이름 변경 / 타입 변경
ver 1.0
id | title | memo |
... | ... | ... |
ver 1.1
id | title | memo | favorite |
... | 고래밥 | 안녕 | Bool |
favorite을 추가해주면 1.0 버전과 1.1버전이 일치하지 않기 때문에 기존에 사용하던 유저는 런타임오류를 겪게되어 삭제하고 다시 다운을 받아야한다.
때문에 마이그레이션이 필요한 것이다!!!
ver 1.2
id | HeadLine | Content |
... | ... | 고래밥 안녕 |
마이그레이션 할 경우 이처럼 사용자의 데이터를 가져와 합치는 것도 가능하다.
1-2 마이그레이션의 단점? (feat. 레거시코드)
버전 1.0이 현재 2.0상태라면 1.1, 1.2, 1.3 등 업데이트가 10번 정도 있었다고 가정한다.
그런데 업데이트를 오랫동안 안 한 유저 1.0 버전을 가지고 업데이트를 진행하면 바로 2.0으로 업데이트 되는게 아니라 1.1, 1.2, 1.3 ... 을 거쳐서 업데이트 해야한다.
때문에 업데이트를 순차적으로 진행해줘야 하므로 모든 버전에 대한 마이그레이션 코드를 가지고 있어야 하는 것이다.
처음에 DB설계를 잘해야하는 이유이다.(말이야 쉽지...)
처음에 심사숙고후 DB를 설계하고 후에 마이그레이션은 최대한 안 할 수 있게 하는 것이 좋은방향이다.
1-3. 실습
실습은 전에만든 메모프로젝트에서 실습을 진행해보도록 하겠습니다.
가장 먼저 Realm과 스키마의 버전을 불러오도록 선언해줍니다.
// In RealmModel
class UserMemo: Object {
@Persisted var memoTitle: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.memoTitle = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
// In ViewController
override func viewDidLoad() {
super.viewDidLoad()
// 1. fileURL
print("FileURL: \(repository.localRealm.configuration.fileURL)")
// 스키마 버전
do {
let version = try schemaVersionAtURL(repository.localRealm.configuration.fileURL!)
print(version)
} catch {
print(error)
}
}
1-3-1. 렘 컬럼추가 및 삭제
ver 1.0 / schema 0
PK | memoTitle | memoDate | memoSubtitle | memoContent | favorite |
// In RealmModel
class UserMemo: Object {
@Persisted var memoTitle: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted var count: Int // 추가
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.memoTitle = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
// In AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
aboutRealmMigration()
return true
}
}
extension AppDelegate {
func aboutRealmMigration() {
// deleteRealmIfMigrationNeeded: 마이그레이션이 필요한 경우 기존 렘 삭제(Realm Browser 닫고 다시 열기!)
// 출시전에 반드시 제거
let config = Realm.Configuration(schemaVersion: 1, deleteRealmIfMigrationNeeded: true)
Realm.Configuration.defaultConfiguration = config
}
}
코드를 실행하면 다음과 같은 오류가 발생합니다.
왜냐하면 Realm 쪽에 새로운 컬럼이 추가 되었기 때문입니다.
이 때는 기존에 URL을 통해 열어두었던 Realm을 끄시고 다시 실행하시면 됩니다.
그러면 새로운 컬럼 count가 생긴 것을 확인하실 수 있습니다.
그리고 스키마 버전도 1로 늘어난 것을 확인하실 수 있습니다.
ver 1.1 / schema 1
PK | memoTitle | memoDate | memoSubtitle | memoContent | favorite | count |
❗️ 주의사항 ❗️
deleteRealmIfMigrationNeeded: true
부분은 기존 렘을 삭제하고 다시 만드는 것이기 때문에 출시할 때는 반드시 이를 제거해줘야한다.
추가한 것을 삭제하고 싶을 때엔 그대로 column을 삭제해준 후 빌드해주면 됩니다.
class UserMemo: Object {
@Persisted var memoTitle: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
// @Persisted var count: Int
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.memoTitle = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
// In AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
aboutRealmMigration()
return true
}
}
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 2, deleteRealmIfMigrationNeeded: true)
Realm.Configuration.defaultConfiguration = config
}
}
ver 1.2 / schema 2
PK | memoTitle | memoDate | memoSubtitle | memoContent | favorite |
그럼 다음과 같이 count가 삭제되고 스키마 버전이 2가 됩니다.
1-3-2 마이그레이션은 하나씩 업데이트
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 2) { migration, oldSchemaVersion in
// 각각 버전에서 달라진 부분 명시
// 하나씩 버전을 타서 업데이트 해야되기 때문에 각각 if로 해주어야함
if oldSchemaVersion < 1 {
}
if oldSchemaVersion < 2 {
}
}
}
}
위에서 말했다시피 마이그레이션은 1.0 -> 2.0으로 되는 것이 아니라 1.0 -> 1.1 -> 1.2 -> ... -> 2.0이 되는 방식이다.
때문에 if문을 써서 하나씩 거쳐갈 수 있도록 해주어야한다.
1-3-3. 컬럼매개변수 변경
이번에는 컬럼 매개변수를 바꿔 보겠습니다. memoTitle을 title로 바꿔보겠습니다.
class UserMemo: Object {
@Persisted var title: String // 기존 memoTitle -> title로 변경
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.title = memoTitle // self.memotitle -> self.title로 변경
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 3) { migration, oldSchemaVersion in
// 각각 버전에서 달라진 부분 명시
// 하나씩 버전을 타서 업데이트 해야되기 때문에 각각 if로 해주어야함
if oldSchemaVersion < 1 {
}
if oldSchemaVersion < 2 {
}
if oldSchemaVersion < 3 {
migration.renameProperty(onType: Todo.className(), from: "importance", to: "favorite")
}
}
Realm.Configuration.defaultConfiguration = config
}
}
ver 1.3 / schema 3
PK | title | memoDate | memoSubtitle | memoContent | favorite |
1-3-4. 컬럼 데이터 합쳐주기
class UserMemo: Object {
@Persisted var title: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted var memoDescription: String // 새로 추가된 부분
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.title = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 3) { migration, oldSchemaVersion in
// 각각 버전에서 달라진 부분 명시
// 하나씩 버전을 타서 업데이트 해야되기 때문에 각각 if로 해주어야함
if oldSchemaVersion < 1 {
}
if oldSchemaVersion < 2 {
}
if oldSchemaVersion < 3 {
migration.renameProperty(onType: Todo.className(), from: "importance", to: "favorite")
}
if oldSchemaVersion < 4 {
migration.enumerateObjects(ofType: Todo.className()) { oldObject, newObject in
guard let new = newObject else { return }
guard let old = oldObject else { return }
new["userDescription"] = "안녕하세요 \(old["title"])의 중요도는 \(old["favorite"]!)입니다"
}
}
}
Realm.Configuration.defaultConfiguration = config
}
}
ver 1.4 / schema 4
PK | title | memoDate | memoSubtitle | memoContent | favorite | memoDescription |
1-3-5. 새로 컬럼 추가후 기본값 넣기
class UserMemo: Object {
@Persisted var title: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted var memoDescription: String
@Persisted var count: Int
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.title = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 5) { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
}
if oldSchemaVersion < 2 {
}
if oldSchemaVersion < 3 {
migration.renameProperty(onType: UserMemo.className(), from: "memoTitle", to: "title")
}
if oldSchemaVersion < 4 {
migration.enumerateObjects(ofType: UserMemo.className()) { oldObject, newObject in
guard let new = newObject else { return }
guard let old = oldObject else { return }
new["memoDescription"] = "안녕하세요 \(old["title"])은 \(old["memoDate"])에 작성되었습니다"
}
}
if oldSchemaVersion < 5 {
migration.enumerateObjects(ofType: UserMemo.className()) { oldObject, newObject in
guard let new = newObject else { return }
new["count"] = 100
}
}
}
Realm.Configuration.defaultConfiguration = config
}
}
ver 1.5 / schema 5
PK | title | memoDate | memoSubtitle | memoContent | favorite | memoDescription | count |
1-3-6. 형변환하기
class UserMemo: Object {
@Persisted var title: String
@Persisted var memoDate = Date()
@Persisted var memoSubtitle: String
@Persisted var memoContent: String
@Persisted var favorite: Bool
@Persisted var memoDescription: String
@Persisted var count: Double // Int -> Double
@Persisted(primaryKey: true) var objectID: ObjectId
convenience init(memoTitle: String, memoDate: Date, memoSubtitle: String, memoContent: String, favorite: Bool) {
self.init()
self.title = memoTitle
self.memoDate = memoDate
self.memoSubtitle = memoSubtitle
self.memoContent = memoContent
self.favorite = false
}
}
extension AppDelegate {
func aboutRealmMigration() {
let config = Realm.Configuration(schemaVersion: 6) { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
}
if oldSchemaVersion < 2 {
}
if oldSchemaVersion < 3 {
migration.renameProperty(onType: UserMemo.className(), from: "memoTitle", to: "title")
}
if oldSchemaVersion < 4 {
migration.enumerateObjects(ofType: UserMemo.className()) { oldObject, newObject in
guard let new = newObject else { return }
guard let old = oldObject else { return }
new["memoDescription"] = "안녕하세요 \(old["title"])은 \(old["memoDate"])에 작성되었습니다"
}
}
if oldSchemaVersion < 5 {
migration.enumerateObjects(ofType: UserMemo.className()) { oldObject, newObject in
guard let new = newObject else { return }
new["count"] = 100
}
}
if oldSchemaVersion < 6 {
migration.enumerateObjects(ofType: UserMemo.className()) { oldObject, newObject in
guard let new = newObject else { return }
guard let old = oldObject else { return }
// 새로 생기는 컬럼에 double로 바꾼 값 넣어주기
// Int -> Double은 무조건 성공이기 때문에 형변환 필요X
new["count"] = old["count"]
}
}
}
Realm.Configuration.defaultConfiguration = config
}
}
// 형변환 다른 케이스
if oldSchemaVersion < 6 {
migration.enumerateObjects(ofType: Todo.className()) { oldObject, newObject in
guard let new = newObject else { return }
guard let old = oldObject else { return }
// 옵셔널이라 nil이라면?
new["count"] = old["count"] ?? 0
if old["count"] < 5 {
new["count"] = 5.5
}
}
}
ver 1.6 / schema 6
PK | title | memoDate | memoSubtitle | memoContent | favorite | memoDescription | count(Double) |
1-3-7. 새로운 테이블이 추가되는 경우엔??
따로 마이그레이션을 해줄 필요는 없고 스키마버전만 올려주면 됩니다.
1-3-8. Realm 테이블이 여러개라면?
연관성이 없는 경우엔?
-> 하나만 수정해주면 되니까 크게 상관 없다
연관성이 있는 경우?
-> 연관된 테이블일 경우 신경을 많이... 써주어야함(리스트)
1-3-9. 옵셔널 -> 논 옵셔널
favorite(Int?) | Double |
---|---|
1 | 1.0 |
2 | 2.0 |
nil | ? |
nil | ? |
5 | 5.0 |
new["favorite"] = old["favorite"] ?? 1.0
'iOS' 카테고리의 다른 글
[iOS] GCD 누구냐 넌 - 2. sync/async & main/global (0) | 2022.12.29 |
---|---|
[iOS] GCD 누구냐 넌 - 1. GCD의 등장배경 (0) | 2022.12.26 |
[iOS] - Push Notification(feat. Firebase) (0) | 2022.10.11 |
[iOS] Map Kit View 사용하기 - 1 (0) | 2022.08.11 |
[iOS] User Notification Ⅰ. 로컬 알림(Local Notification) (0) | 2022.07.29 |