josee2
josee2 Devlog
josee2
전체 방문자
오늘
어제
  • 분류 전체보기 (54)
    • Swift (33)
      • 문법 (33)
    • iOS (13)
    • Algorithm (3)
      • 프로그래머스 (1)
      • BOJ (0)
      • 기타 (2)
    • 일상 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 프로세스
  • 서울청년취업사관학교
  • GCD
  • 스위프트
  • Swift
  • 직렬
  • Async
  • 문자열 템플릿
  • 동시큐
  • SeSAC
  • 비동기
  • 동기
  • concurrent
  • 동시
  • serial
  • IOS
  • 멀티쓰레드
  • 동시성프로그래밍
  • Sync
  • 쓰레드

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
josee2

josee2 Devlog

iOS

[iOS] - Realm 마이그레이션 정리

2022. 10. 13. 19:13

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
    'iOS' 카테고리의 다른 글
    • [iOS] GCD 누구냐 넌 - 2. sync/async & main/global
    • [iOS] GCD 누구냐 넌 - 1. GCD의 등장배경
    • [iOS] - Push Notification(feat. Firebase)
    • [iOS] Map Kit View 사용하기 - 1
    josee2
    josee2
    iOS 개발자 지망생의 공부기록입니다.

    티스토리툴바