본문 바로가기
DataBase/IOS - Realm

IOS)Realm - Migration

by 후르륵짭짭 2022. 5. 23.
728x90
반응형

일본 기타큐슈 어딘가에서

안녕하세요. 후르륵짭짭 입니다.

이번에는 Realm의 Migration에 대해 알아보려고 합니다.

 

** Migration이란 ** 

출처 : https://fromleaf.tistory.com/65

현업을 하다 보면 Migration이라는 말을 많이 듣게 됩니다.

Migration이란 쉽게 기존에 있던 것에서 다른 곳으로 옮기거나 소프트웨어의 업데이트가 있을 때, 구축되어 있는 데이터베이스의 변동이 있을 때 사용합니다. 

즉, 기존의 있던 사항이 변경 된다고 할 때 마이그레이션이라는 말을 사용합니다.

 

** Realm Auto Migration **

class MigrationTestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        do {
            let realm = try Realm()
            print("realm = \(configuration.fileURL)")
            let book = Book(firstName: "ali", userId: 23)
            try! realm.write({
                realm.add(book)
            })
        }
        catch{
            print(error.localizedDescription)
        }
    }

}

class Book : Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var userId = 0
    
    override static func primaryKey() -> String? {
        return "userId"
    }
    
    convenience init(firstName : String, userId : Int){
        self.init()
        self.firstName = firstName
        self.userId = userId
    }
}

위와 같이 코드가 적혀있는 상태에서, Build and Run을 하면 SchmaVersion이 1인 Realm File이 생성이 됩니다.

그리고 나서 Book 이라는 Realm Class에 title을 추가고 Run 을 하면 Realm에 title이 추가되었다고 오류가 발생합니다.

이런 크래시가 발생한 것은 생성된 Realm 파일의 구조와 달라져서 크래시가 발생한 겁니다.

class Book : Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var userId = 0
    @objc dynamic var title = ""
    
    override static func primaryKey() -> String? {
        return "userId"
    }
    
    convenience init(firstName : String, userId : Int){
        self.init()
        self.firstName = firstName
        self.userId = userId
    }
}

이럴 경우에는 Realm Schema version을 2로 변경 해고 configuration으로 등록을 해주면 

자동으로 구조 변경 된 것을 확인하고 생성된 Realm 파일의 구조를 변경해서 크래시가 발생하지 않고 사용할 수 있게 됩니다.

override func viewDidLoad() {
        super.viewDidLoad()
        do {
            let configuration = Realm.Configuration(schemaVersion:2)
            let realm = try Realm(configuration: configuration)
            print("realm = \(configuration.fileURL)")
            let book = Book(firstName: "ali", userId: 23)
            try! realm.write({
                realm.add(book)
            })
        }
        catch{
            print(error.localizedDescription)
        }
    }

 

** Realm Custom Migration ** 

Custom Migration은 Auto Migration이 할 수 있는 간단한 필드 추가에서 벗어나

필드 이름 변경 , 기존 데이터 값 추가 , 디폴트 관계 데이터 추가 등 여러 수작업이 필요할 때 사용합니다.

이는 Configuration 변경을 통해 가능하게 할 수 있습니다. 

Realm.Configuration(schemaVersion:4) { migration, oldSchemaVersion in }

schemaVersion -> 현재 스키마 버전

migration -> Migration객체로 Realm 파일에 접근 할 수 있으며 Migration에 필요한 메소드 포함하고 있음 

oldSchemaVersion -> 스키마 버전 비교

 

- Default 값 추가 -

위에 처럼 Auto Migration을 사용하면 안전하게 필드가 추가됩니다.

하지만 추가 되는 데이터들에게 Default값을 추가해야한다면 Custom Migration을 해줘야합니다.

class MigrationTestViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        do {
            let configuration = Realm.Configuration(schemaVersion: 2) { migration, oldSchemaVersion in
                print("New Schema : \(migration.newSchema.description)")
                print("old Schema : \(migration.oldSchema.description)")
                //title의 Default값을 넣어준다.
                migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
                    newObject?["title"] = "default"
                }
            }
            let realm = try Realm(configuration: configuration)
            print("realm = \(configuration.fileURL)")
            let book = Book(firstName: "ali", userId: 20)
            try! realm.write({
                realm.add(book)
            })
        }
        catch{
            print(error.localizedDescription)
        }
    }

}

 

 

- 필드 Name 변경 -

기존의 필드 이름이 변경이 되어야한다면 아래와 같이 변경해주면 됩니다.

이때 새로 작성되는 Schmaversion에는 Model의 명을 변경 해줘야합니다.

class Book : Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var userId = 0
    
    @objc dynamic var title : String? = nil
//    @objc dynamic var currentUser = false
    @objc dynamic var iscurrentUser = false
    
    override static func primaryKey() -> String? {
        return "userId"
    }
    
    convenience init(firstName : String, userId : Int){
        self.init()
        self.firstName = firstName
        self.userId = userId
    }
}
let configuration = Realm.Configuration(schemaVersion:2) { migration, oldSchemaVersion in
        migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
        //SchemaVersion 비교
            if oldSchemaVersion == 1 {
            // oldObject에 title 필드 값이 없다면 Default값 넣어준다.
                if oldObject?["title"] == nil {
                    newObject?["title"] = "migrated Value"
                }
            }
        }
        //currentUser로 되어 있는 필드 이름을 iscurrentUser로 변경
        migration.renameProperty(onType: "Book", from: "currentUser", to: "iscurrentUser")
    }

 

- 디폴트 관계 데이터 추가 및 통합 스키마 버전 관리  - 

class Book : Object {
    @objc dynamic var firstName = ""
    @objc dynamic var lastName = ""
    @objc dynamic var userId = 0
    
    @objc dynamic var title : String? = nil
    @objc dynamic var iscurrentUser = false
    @objc dynamic var passport : Passport?
    
    override static func primaryKey() -> String? {
        return "userId"
    }
    
    convenience init(firstName : String, userId : Int){
        self.init()
        self.firstName = firstName
        self.userId = userId
    }
}

class Passport : Object {
    @objc dynamic var passportNumber = ""
}
let configuration = Realm.Configuration(schemaVersion:3) { migration, oldSchemaVersion in
    migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
        if oldSchemaVersion == 1 {
            if oldObject?["title"] == nil {
                newObject?["title"] = "migrated Value"
            }
            migration.renameProperty(onType: "Book", from: "currentUser", to: "iscurrentUser")
        }
    }

    if oldSchemaVersion == 2 {
    // 추가 되는 관계형 필드인 Passport에 디폴트 값을 추가할 때 
        migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
            let passport : MigrationObject = migration.create("Passport", value: ["passportNumber" : "Number"])
            newObject?["passport"] = passport
        }
    }
}

위에 처럼 스키마 버전 관리를 하나씩 분기 처리를 하면 놓치는 부분이 많을 수 있기 때문에

아래 처럼 비교를 통해 해주는 것이 바람직합니다.

let configuration = Realm.Configuration(schemaVersion:4) { migration, oldSchemaVersion in
	//이렇게 통합적으로 관리 해주는게 좋습니다.
    if oldSchemaVersion < 4 {
        migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
            if oldObject?["title"] == nil {
                newObject?["title"] = "migrated Value"
            }
            migration.renameProperty(onType: "Book", from: "currentUser", to: "iscurrentUser")
        }

    migration.enumerateObjects(ofType: "Book") { oldObject, newObject in
        let passport : MigrationObject = migration.create("Passport", value: ["passportNumber" : "Number"])
        newObject?["passport"] = passport
    }

    }

 

Realm의 Migration에 대해 알아봤습니다.

DB의 Migration은 필수적인 항목이고 피해갈 수 없죠.

저도 많이 고생을 했습니다.

개인적인 생각이지만, 한번 DB 구조를 잘짜야지 Custiom Migration을 안하게 되고 효율적인 데이터베이스 구조를 만들 수 있습니다.

감사합니다.

 

참고 사이트 :

http://www.clusterdb.com/mongodb/migrating-your-ios-apps-realm-schema-in-production

 

Migrating Your iOS App’s Realm Schema in Production | Andrew Morgan on Databases

Migrating Your iOS App’s Realm Schema in Production Introduction Murphy’s law dictates that as soon as your mobile app goes live, you’ll receive a request to add a new feature. Then another. Then another. This is fine if these features don’t requir

www.clusterdb.com

https://ali-akhtar.medium.com/migration-with-realm-realmswift-part-6-11c3a7b24955

 

Migration with Realm (RealmSwift Part 6)

In this part we will cover following topics

ali-akhtar.medium.com

 

728x90
반응형

댓글