I\'m using realm.io in a swift app. This is the first time I\'ve had to run a migration since I have an app in production. I changed one of the models and added a couple of
It looks like you flat-out copied the code out of the Realm Swift 'Migration' example and pasted it verbatim. A lot of that code is to actually 'set-up' a new demo migration each time the sample app is run, and not actually necessary for a normal Realm migration.
There are two components to a Realm migration:
Bump the schema version number in the Configuration
object. Realm files start at version 0, and everytime you want to do a new migration, increase it by 1.
Supply a migration block that will be executed multiple times for each increment of your Realm schema version. While the block itself is necessary, its purpose is to let you copy/transform any data in an older Realm file. If you're simply adding new fields, you can just leave the block empty.
If your UIViewController
is using Realm at all in its implementation, it's best to place your migration code before the UIWindow
code to ensure the migrations have already occurred before the UIViewController
starts using it (Otherwise you'll get an exception).
Since all you're doing is adding a few fields to it, this is all the code you need:
let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
//Leave the block empty
}
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 1, migrationBlock: migrationBlock)
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = UIViewController()
window?.makeKeyAndVisible()
This works as intended, throwing exceptions when the downgrading (if working with branches where the versions differ) etc. Some sample migrations are shown too.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
// Update this number for each change to the schema.
schemaVersion: 4,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 2 {
migration.enumerate(SomeObject.className()) { oldObject, newObject in
newObject!["newColumn"] = Enum.Unknown.rawValue
}
}
if oldSchemaVersion < 3 {
migration.enumerate(SomeOtherObject.className()) { oldObject, newObject in
newObject!["defaultCreationDate"] = NSDate(timeIntervalSince1970: 0)
}
}
if oldSchemaVersion < 4 {
migration.enumerate(SomeObject.className()) { oldObject, newObject in
// No-op.
// dynamic properties are defaulting the new column to true
// but the migration block is still needed
}
}
})
Obviously that won't compile as is with the SomeObject
etc., but you get the idea :)
This ended up being the solution. I cannot say that I came up with it on my own because I didn't. I got some excellent help from a wonderful engineer named Claire at realm.
Here's what needed to be done:
class RoomsViewController: UIViewController, UITableViewDelegate {
var activeRoom = -1
var room: Room? = nil
var array = [Room]()
lazy var realm:Realm = {
return try! Realm()
}()
var notificationToken: NotificationToken?
@IBOutlet weak var roomsTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
array = Array(realm.objects(Room.self))
setupUI()
// Set realm notification block
notificationToken = realm.addNotificationBlock { [unowned self] note, realm in
// TODO: you are going to need to update array
self.roomsTable.reloadData()
}
}
This is first view controller that get's loaded and it was immediately querying the realm database to build the array. With Claire's suggestion, Realm was lazy loaded (or tried) and the code to build the array was moved into the viewDidLoad method whereas before it was called at the top.
This allowed the realm to migration to run ahead. As you might imagine, I honestly assumed that the view never got loaded until after the application
function loaded in the AppDelegate
. However, I guess I was wrong.
So, this will solve it. If you ever happen to run into the same problem, give this a shot.
UPDATE:
Here's how the appDelegate function looks now:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Inside your application(application:didFinishLaunchingWithOptions:)
let config = Realm.Configuration(
// 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: 1,
// 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) {
migration.enumerate(Inventory.className()) { oldObject, newObject in
// No-op.
// dynamic properties are defaulting the new column to true
// but the migration block is still needed
}
migration.enumerate(Profile.className()) { oldObject, newObject in
// No-op.
// dynamic properties are defaulting the new column to true
// but the migration block is still needed
}
migration.enumerate(Room.className()) { oldObject, newObject in
// No-op.
// dynamic properties are defaulting the new column to true
// but the migration block is still needed
}
migration.enumerate(Box.className()) { oldObject, newObject in
// No-op.
// dynamic properties are defaulting the new column to true
// but the migration block is still needed
}
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
do {
_ = try Realm()
} catch let _ as NSError {
// print error
}
return true
}
I found this to work best in a View Controller:
let migrationBlock: MigrationBlock = { migration, oldSchemaVersion in
//Leave the block empty
}
lazy var realm:Realm = {
Realm.Configuration.defaultConfiguration = Realm.Configuration(schemaVersion: 1, migrationBlock: migrationBlock)
return try! Realm()
}()
In other words, move the configuration assignment into the block so it doesn't get executed too late.