Switching between realms (iOS / Swift 3) - ios

I was trying to log in or register a user and connect them to an existing realm. Then, depending on the info stored in that realm, I may need them to instead connect to a different realm.
Is it not possible to try! Realm with a different configuration after it is initially configured? Is it discouraged? Does it need to be done outside of the initial DispatchQueue?
Here is the code:
SyncUser.logIn(with: usernameCredentials, server: URL(string: "http://11.22.333.0:9080")!) {
user, error in
guard let user = user else {
fatalError(String(describing: error))
}
DispatchQueue.main.async {
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://11.22.333.0:9080/ab56realmURL/NameOfRealm1")!)
)
self.realm = try! Realm(configuration: configuration)
if (someCheckOfData in realm) {
let configuration2 = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://11.22.333.0:9080/ab56realmURL/NameOfRealm2")!)
)
self.realm = try! Realm(configuration: configuration2)
}
}
}
Thanks so much for any help!

No, it's not discouraged. All you're doing here is creating 2 discrete copies of Configuration, which will then subsequently be creating 2 separate Realm instances on your server.
The two will be completely separate, so there's no chance of causing an exception by incorrectly changing the configuration after it was used to create an initial Realm instance.
One thing we do recommend though is not holding onto specific Realm references like that though. They are not thread safe, and GCD isn't guaranteed to execute the same queues on the same threads, so you may be setting yourself up for a future exception.
If this is going to be your primary Realm, it's usually recommended to set that Configuration as the default Realm one. Otherwise, since Configuration is thread-safe (Assuming you don't modify it later), you can hold onto that, and use it to try! Realm(configuruation:) whenever you actually need to use Realm.

Related

Swift 3 updating core data model causes crash for external testers

I released an app using core data to store some important information.
Recently I decided to re-do my data model to bring it up to date and make it easier to use.
I added some entities to the data model, and it runs fine in the simulator - however when I released it to the beta testers as soon as it tries to do anything with core data it is crashing.
I did not create a new version of the data model.
I have read here and here about how to deal with this error but both answers reference code that I do not have anywhere in my app, but they seem to have built in - they also talk about lightweight data migration? A lot of the answers reference a NSPersistentStoreCoordinator, which I do not have/know how to implement.
The code I have in the app delegate dealing with the persistentContainer is:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
The other answers also referenced crashes in the simulator which required re-installation - I don't think I got these, but I may not have noticed.
What is the best way for me to update my data-model such that my users won't get crashes?
EDIT:
I have updated the persistentContainer to this:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "App_Name")
let myFileManager = FileManager()
do {
let docsurl = try myFileManager.url(for:.documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let myUrl = docsurl.appendingPathComponent("UserDataTA")
if try myFileManager.contentsOfDirectory(atPath: docsurl.path).contains("UserDataTA") == false {
try myFileManager.createDirectory(at: myUrl, withIntermediateDirectories: false, attributes: nil)
try container.persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: myUrl, options: nil)
}
} catch {
print(error)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalErrorText = error.debugDescription
firstFuncs.errorAlert(error: fatalErrorText)
}
})
return container
}()
however now the error message is "Error Domain=NSCocoaErrorDomainCode=134140 'Persistent store migration failed, missing mapping model.'"
Your crash sounds like it's happening because your data model changed, and any existing data that exists on a device can't be mapped 1:1 to the new model. This is where data mapping and migration comes in. Apple's docs are a good place to start. The very high-level overview is:
Open your xcdatamodel or xcdatamodeld file in Xcode, then select "Add Model Version...".
If necessary, save your model as an xcdatamodeld file. (The d suffix indicates that it's versioned.)
Note that your xcdatamodeld file is really a folder, and it includes multiple xcdatamodel files. All versions after the first one will include a numeric version number in the filename.
Make your model changes in the new model version.
If the changes are relatively simple (such as renaming an entity or adding or removing attributes), then it's considered a lightweight migration, and core data can figure out how to do the migration itself.
If your changes are more involved (such as splitting an entity type into two), then you must create a mapping model.
Add the code to perform the migration at startup time.
Obviously there's a lot more to this process, but this may give you a decent start.

Realm migration inadequately documented. Can anyone clarify?

Performing migrations on a Realm database is poorly documented and the documentation seems to be out-of-date. There are two areas explaining how to migrate data:
-- The simple example on the Realm website: https://realm.io/docs/swift/latest/
-- A more detailed example in the Github examples section: https://github.com/realm/realm-cocoa/blob/master/examples/ios/swift-3.0/Migration/AppDelegate.swift
Neither of these examples adequately explain how to migrate data between schema versions. I've tried playing around with the examples and have yet to get any migrations working. As well, I've had problems with app crashes when upgrading to newer Realm versions without schema changes and data changes, which don't occur in the Simulator but occur when installing the app from TestFlight or the App Store.
Seems like the Realm documentation and examples detailing migrations are due for a refresh. My areas of interest are:
Upgrading to a newer Realm version without a schema change in the database. Unclear whether I should continue using the default.realm file generated with a previous version, or whether I need to re-generate the default.realm file using the newer Realm framework version.
Adding a new attribute to a Realm object.
New objects ("rows") added to an existing class without any schema change.
No schema changes to existing classes in the database, but addition of an entirely new class or classes.
Any combination of the above.
Thanks!
Sorry the docs are not sufficient. We appreciate the feedback and will use it to improve them. In the mean time, let me answer your questions:
You do not need to do anything when you upgrade the SDK. Sometimes, we upgrade the core database file format, but this migration happens automatically when you open the Realm (Realm()) so you don't have to worry about it.
When you add a new property to an object you can just follow this code snippet.
Nothing is needed in the migration block since this block is simply to apply data transformations between versions. All you need to do is increment the schemaVersion
// 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) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// 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
let realm = try! Realm()
Adding objects to a Realm does not affect the schema so a migration is not relevant.
This is the same as 2, you simply need to increment the schemaVersion but you don't have to do anything in the migration block since Realm handles everything. The migration block is for custom migration logic where, for example, you want to transform firstName and lastName from schemaVersion=0 to fullName when updating to schemaVersion=1. In this case, you could get the data from the old version and concatenate the strings into the new fullName property within the migration block.
Hope this helps!
Unfortunately, that doesn't work, at all. There is a block of code I had to add to iOS apps several months ago in order to get Realm to work, but it doesn't solve the migration problems I've experienced:
config.fileURL = config.fileURL!.deletingLastPathComponent().appendingPathComponent("default-v1.realm")
Realm.Configuration.defaultConfiguration = config
// copy over old data files for migration
//let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
//print("Realm.Configuration.defaultConfiguration.fileURL! = \(defaultURL)")
//let defaultParentURL = defaultURL.URLByDeletingLastPathComponent
if let realmURL = bundleURL("default-v1") {
do {
//print("Bundle location for default.realm = \(realmURL)")
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = URL(fileURLWithPath: path)
let filePath = url.appendingPathComponent("default-v1.realm").path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE INSTALLED")
print("Documents location for default.realm = \(filePath)")
} else {
//print("FILE NOT INSTALLED, COPYING default.realm from bundle to Documents directory.")
try FileManager.default.copyItem(at: realmURL, to: defaultURL)
}
} catch {}
}
I suspect that a new default-v1.realm file may clobber an existing default-v1.realm file on a user's device, using this code with the "Nothing is needed" migration method.
Part of the confusion stems from the example migration code at https://github.com/realm/realm-cocoa/tree/master/examples/ios/swift-3.0/Migration where data from the v1 file is getting copied over to the v2 file. Regardless, I've tried the "Nothing is needed" code you've posted and the app doesn't find any Realm data then crashes.
It looks like I could splice the code you've posted into the code I've been using then perhaps that would solve the problem. Will look into that next week.
Thanks for your response!

iOS Swift Realm Sync Issue

I used this configuration code for local storage.
var configuration = Realm.Configuration.defaultConfiguration
configuration.encryptionKey = getKey() as Data
I used this configuration code for sync with server.
let syncServerURL = URL(string: serverUrl + objectName!)!
var configuration = Realm.Configuration.defaultConfiguration
configuration.encryptionKey = getKey() as Data configuration.syncConfiguration = SyncConfiguration(user: SyncUser.current!, realmURL: syncServerURL)
I create some data without sync, it is saved locally. However, if I turn on sync(different configuration), the previously created data(locally) is not synced to the server? How to sync already saved data?
Realms are uniquely referenced by one of three mutually exclusive Realm.Configuration properties:
fileURL
inMemoryIdentifier
syncConfiguration
Realm configurations with any of these properties with different values will refer to separate Realms.
So your initial Realm has a fileURL value (with nil for the other two properties), whereas your second Realm has a syncConfiguration value (with nil for the other two properties) so they refer to separate Realms.
If you wish to copy data from the first (local) Realm to the second (synced) Realm, you may do so using Realm's APIs for reading objects & creating objects just like you would for any other data.

Realm migration when using non-default configurations

In the realm docs I see the code example for migrations
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) {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
})
// Tell Realm to use this new configuration object for the default Realm
Realm.Configuration.defaultConfiguration = config
and then it says to use let realm = try! Realm() to get the realm instance. However, in our application we are using our own realm configurations with something like
let realm = try! Realm(configuration: RealmConfig.getConfig(typeURL: .userData)!)
We have a few different configurations besides .userData. My question is, how does one go about doing migrations with these non-default configurations? The code example really only shows how to set the default configuration which is insignificant for my use. I couldn't find anything like
Realm.Configuration.userData = config
Does something like this exist that I am missing? Or is there another way I'm supposed to go about this?
You can instantiate different Realm instances with different configurations like this:
let userDataConfiguration = Realm.Configuration(...)
let userDataRealm = try! Realm(configuration: userDataConfiguration)
Learn more in docs at https://realm.io/docs/swift/latest/#realms

Swift - Realm mobile platform today extension

I'm building a Notes app with today extension.
I want to link my app's data with today widget using App Group.
To do this, I have to change the default realm's fileURL to App Group URL.
Without syncConfiguration it works very well, but when I use this, the default realm's fileURL did not changed.
So my Questions is how can I change Realm.Configuration.defaultConfiguration.fileURL using realm mobile platform!!!
let file = FileManager.default
let directory: NSURL = file.containerURL(forSecurityApplicationGroupIdentifier: "group.com.myapp")! as NSURL
let realmPath = directory.appendingPathComponent("default.realm")
Realm.Configuration.defaultConfiguration = Realm.Configuration(
fileURL: realmPath,
syncConfiguration: SyncConfiguration(
user: user,
realmURL: URL(string: "realm://myserver:9080/~/myNote")!
),
objectTypes: [realmNote.self, Tag.self]
)
self.realm = try! Realm()
please help!!!...
If you set a sync configuration on the Configuration object, you cannot set a custom file URL. The sync configuration, file URL, and in-memory identifier are mutually exclusive settings. This is by design.
If you need it to work otherwise, please feel free to file a ticket at our GitHub repository, and we'll take a look.

Resources