肇鑫的技术博客

业精于勤,荒于嬉

Realm模型升级(二)

Realm模型升级(一)中讲了对象添加了新属性之后要如何升级。最近我又遇到了新的问题,要将对象改名。

新问题

因为业务的需要,必须将代码

class Item:Object {
    static let calendar = Calendar(identifier: .gregorian)
    
    @objc dynamic var title = ""
    @objc dynamic var addedDate:Date = Date()
    @objc dynamic var relatedItem:Item? = nil
    let records = List<Record>()
    
    override class func primaryKey() -> String? {
        return "title"
    }
}

class Record:Object {
    static let dateFormatter:DateFormatter = {
        let df = DateFormatter()
        df.locale = Locale(identifier: "zh")
        df.dateStyle = .medium
        df.timeStyle = .none
        
        return df
    }()
    
    @objc dynamic var id:Int = 1
    @objc dynamic var addedDate:Date = Date()
}

更改为

class ACItem:Object {
    static let calendar = Calendar(identifier: .gregorian)
    
    @objc dynamic var title = ""
    @objc dynamic var addedDate:Date = Date()
    @objc dynamic var relatedItem:ACItem? = nil
    let records = List<ACRecord>()
    
    override class func primaryKey() -> String? {
        return "title"
    }
}

class ACRecord:Object {
    static let dateFormatter:DateFormatter = {
        let df = DateFormatter()
        df.locale = Locale(identifier: "zh")
        df.dateStyle = .medium
        df.timeStyle = .none
        
        return df
    }()
    
    @objc dynamic var id:Int = 1
    @objc dynamic var addedDate:Date = Date()
}

即原来的类的名称之前,需要添加AC字样。更改了代码之后运行,发现数据不见了。

分析

打开数据库文件查看(如图)。原来,虽然代码中的类变了,但是数据库中的类还是原来的名字,需要手动迁移。

realm database

解决方案

23-39行,利用之前的Item对象,生成ACItem对象;利用Item对象中的records属性,生成ACRecord对象。需要注意的事,因为代码中此时已经没有了Item类和Record类,我们没法使用Item.className(),而只能直接使用"Item"

41-43行,删除掉旧数据。

let config:Realm.Configuration = {
    // update realm
    let config = Realm.Configuration(
        fileURL: destinationURL,
        
        // Set the new schema version. This must be greater than the previously used
        // version (if you've never set a schema version before, the version is 0).
        schemaVersion: 3,
        
        // Set the block which will be called automatically when opening a Realm with
        // a schema version lower than the one set above
        migrationBlock: { migration, oldSchemaVersion in
            // We haven’t migrated anything yet, so oldSchemaVersion == 0
            if (oldSchemaVersion < 1) {
                // Nothing to do!
            }
            
            if (oldSchemaVersion < 2) {
                // Nothing to do!
            }
            
            if (oldSchemaVersion < 3) {
                migration.enumerateObjects(ofType: "Item") { (oldObject, _) in
                    let acItem = migration.create(ACItem.className())
                    acItem["title"] = oldObject!["title"]
                    acItem["addedDate"] = oldObject!["addedDate"]
                    
                    guard let list = acItem["records"] as? List<MigrationObject>,
                        let oldList = oldObject!["records"] as? List<MigrationObject> else {
                        fatalError()
                    }
                    
                    oldList.forEach { o in
                        let acRecord = migration.create(ACRecord.className())
                        acRecord["id"] = o["id"]
                        acRecord["addedDate"] = o["addedDate"]
                        list.append(acRecord)
                    }
                }
                
                // delete
                migration.deleteData(forType: "Item")
                migration.deleteData(forType: "Record")
            }
    })
    
    return config
}()

总结

  1. 代码中对象的改名,意味着数据库中数据的迁移。
  2. MigrationObjectEnumerateBlock中的oldObject,实际上一个词典,我们通过键来进行数据的查找。