Different Realm Configurations Appearing in Swift App - ios

Swift 3, Xcode 8, RealmSwift 2.0.2, Realm Object Server 1.0
In my app delegate, I have a function that sets my Realm configuration to connect to a remote sync server I have set up. I'm just using a test account to authenticate until I can get the basics of sync working. 1.1.1.1 isn't my real IP address. ;)
let username = "test"
let password = "test"
let address = "http://1.1.1.1:9080"
let syncAddress = "realm://1.1.1.1:9080/~/myapp"
SyncUser.authenticate(with: Credential.usernamePassword(username: username, password: password, actions: []), server: URL(string: address)!, onCompletion: { user, error in
guard let user = user else {
fatalError(String(describing: error))
}
// Open Realm
Realm.Configuration.defaultConfiguration = Realm.Configuration(
syncConfiguration: (user, URL(string: syncAddress)!)
)
})
This seems to work fine. I see data appear on my server, and I get no errors. My assumption is that setting the Realm configuration here means that all instances of Realm() will use this configuration.
I then set a realm object as a class property in two separate view controllers:
class TableViewControllerA: UITableViewController{
let realm = try! Realm()
override func viewDidLoad() {
// CORRECT: Prints "nil" as it should for a remotely synced Realm instance
print(realm.configuration.fileURL)
}
}
...and another in another file:
class ViewControllerB: UIViewController{
let realm = try! Realm()
override func viewDidLoad() {
// WRONG: Prints the path to the local realm file in the Simulator
print(realm.configuration.fileURL)
}
}
As noted in the code comments above, the two instances of realm are different. On some of my view controllers, I can save objects to the server and see them appear on my device. On other view controllers, I don't see any data because it's using the wrong Realm database.
Can I not reliably expect a Realm configuration to persist throughout my app? Do I need to do something else to use the same configuration?

You're setting the default configuration within the authentication completion handler. This callback is invoked asynchronously after the user has been authenticated. If an instance of one of your view controller subclasses happens to be created before the callback runs, the Realm it opens will use the default default configuration, prior to any changes you make in your authentication completion handler.

Related

Converting local Realm to synced Realm in the middle of app life cycle (in Swift)

My app will have a paid feature called multi-devices sync. I would like to implement the feature with Realm Cloud - Query Based Sync.
I know how to convert local Realm to synced Realm thanks to
this thread.
But this is based on the scenario that users sync their Realm from the app start - before opening their non-synced local realm. That doesn’t work for me because my users will start sync when they paid for it.
Therefore, I have to convert their local Realm in the middle of app life cycle and the local Realm is already opened by that time.
My issue comes in here. When I try to convert local realm to synced realm, app crashes with this message:
Realm at path ‘…’ already opened with different read permissions.
I tried to find a way to close local Realm before converting it, but Realm cocoa does not allow me to close a Realm programmatically.
Here’s my code converting local Realm to synced Realm.
func copyLocalRealmToSyncedRealm(user: RLMSyncUser) {
let localConfig = RLMRealmConfiguration()
localConfig.fileURL = Realm.Configuration.defaultConfiguration.fileURL
localConfig.dynamic = true
localConfig.readOnly = true
// crashes here
let localRealm = try! RLMRealm(configuration: localConfig)
let syncConfig = RLMRealmConfiguration()
syncConfig.syncConfiguration = RLMSyncConfiguration(user: user,
realmURL: realmURL,
isPartial: true,
urlPrefix: nil,
stopPolicy: .liveIndefinitely,
enableSSLValidation: true,
certificatePath: nil)
syncConfig.customSchema = localRealm.schema
let syncRealm = try! RLMRealm(configuration: syncConfig)
syncRealm.schema = syncConfig.customSchema!
try! syncRealm.transaction {
let objectSchema = syncConfig.customSchema!.objectSchema
for schema in objectSchema {
let allObjects = localRealm.allObjects(schema.className)
for i in 0..<allObjects.count {
let object = allObjects[i]
RLMCreateObjectInRealmWithValue(syncRealm, schema.className, object, true)
}
}
}
}
Any help will be appreciated.
Thanks.
I made a copy of the local realm file and opened the copy with RLMRealmConfiguration. Afterwards, just delete both files. It is not the best solution, but it works

Realm Swift use locally only, however it still tries to connect online

I am following the realm swift getting started guide here and it is working fine. I have the following object:
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
and in my viewcontroller I have
override func viewDidLoad() {
super.viewDidLoad()
print(Realm.Configuration.defaultConfiguration.fileURL!)
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
let realm = try! Realm()
try! realm.write {
realm.add(myDog)
}
}
little snitch reports that realm tries to connect to static.realm.io and api.mixpanel.com. How do I stop realm from attempting to connect to various servers if I only want to use it locally?
It's intended behavior.
Realm collects anonymous analytics when your app is run with a debugger attached, or when it runs in a simulator.
Please see our doc for more details.
https://realm.io/docs/swift/latest/#i-see-a-network-call-to-mixpanel-when-i-run-my-app-what-is-that
It doesn't happen in a release build. To prevent this even in debug build, set environment variable named REALM_DISABLE_ANALYTICS.
See also https://github.com/realm/realm-cocoa/blob/master/Realm/RLMAnalytics.mm#L37-L44

What is the best way of syncing data to a Realm backend on a multiple page iOS application

I have an application with a login screen and then two table views in a tab bar controller once the user has logged in. In each table view, I have data which I would like to sync to my Realm Database.
The setup of my Realm database is heavily based on the following tutorial: https://realm.io/docs/tutorials/realmtasks/
The code for user authentication is as below
SyncUser.logIn(with: userCredentials, server: (url! as URL)) { user, error in
guard user != nil else {
print("Error")
self.logInMessage.text = "Invalid login details, please try again"
self.logInMessage.shake()
return
}
DispatchQueue.main.async {
self.performSegue(withIdentifier: "logInSegue", sender: self)
}
}
In each of my tableviews I aim to have the following code:
DispatchQueue.main.async {
// Open Realm
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://127.0.0.1:9080/~/realmtasks")!)
)
self.realm = try! Realm(configuration: configuration)
}
However, note that I need a user to for the configuration. Should I pass this from the login screen, or is this overkill? Thanks in advance
I would set up your Realm's default configuration when your app launches (even before you authenticate a user). This could go in your AppDelegate or a similar "startup" file.
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://127.0.0.1:9080/~/realmtasks")!)
)
Realm.Configuration.defaultConfiguration = configuration
Then in the first view that loads, you take care of user authentication (this will probably require some UI so your user can log in unless you use iCloud authentication with Realm which can happen behind-the-scenes).
Once you have authenticated a user, you can access their SyncUser object anytime:
//Log out the user (for example)
SyncUser.current?.logOut()
Good luck! :)

Swift Realm: How to replace database at the same path without restarting app

I want to restore the realm swift database the setting section inside my App. I'm able to use FileManager to create and list backups for .realm files. However, when I delete the current realm file, and then copy another realm file to the original realm file location, my App doesn't know recognize that the file content has changed.
In fact, even when I removed the original realm file, and WITHOUT replacing it with anything, my App still functions correctly.
I debugged into the source and I think the issue is that Realm always return the cached version based on the URL. Even if the Url is no longer valid, it still returns the cached realm.
If there any way to force reset the cache so that I can replace the realm file? Seems like Objc allows it, but not in Swift?
Or is there some kind of strong reference that I've missed?
Here's a snippet of the code of deleting the realm file and still being able to access it:
try! Realm().write {
// A bunch of adds
}
let curURL = Realm.Configuration.defaultConfiguration.fileURL!
try! FileManager.default.removeItem(at: curURL)
// Just sample code to make sure things are all cleaned up
autoreleasepool {
let realm = try! Realm(fileURL: curURL) // works
let results = realm.objects(Email.self) // results are from the realm before I delete the file.
}
Well I think it's really the autoreleasing issue that I'm seeing. If I wrap my code like the following, then the database will be removed correctly.
So that means I will really have to hunt down all of reference in my App ...
class Email: Object {
dynamic var subject = ""
dynamic var id = UUID().uuidString
override static func primaryKey() -> String? {
return "id"
}
}
autoreleasepool {
print("\(try! Realm().configuration.fileURL!.path)")
try! Realm().write {
try! Realm().deleteAll()
}
try! Realm().write {
try! Realm().add(Email(value: ["subject": "Hello"]))
try! Realm().add(Email(value: ["subject": "World"]))
try! Realm().add(Email(value: ["subject": "Is"]))
try! Realm().add(Email(value: ["subject": "Good"]))
}
var result = try! Realm().objects(Email.self)
print("\(result.count)")
try! FileManager.default.removeItem(at: Realm.Configuration.defaultConfiguration.fileURL!)
var newResult = try! Realm().objects(Email.self)
print("\(newResult.count)")
}
var newResult = try! Realm().objects(Email.self)
print("\(newResult.count)")

Use single realm instance/variable across the application

Goal: Reduce memory footprint
My approach is to create single realm instance in AppDelegate class & then access that instead of creating a new variable each time.
AppDelegate
lazy var realm: Realm = {
let realm = try! Realm()
// Get our Realm file's parent directory
if let folderPath = realm.configuration.fileURL?.URLByDeletingLastPathComponent?.path{
// Disable file protection for this directory
do {
try NSFileManager.defaultManager().setAttributes([NSFileProtectionKey: NSFileProtectionNone],ofItemAtPath: folderPath)
}catch {
printDebug(error)
}
}
return realm
}()
UIViewController
var realm = (UIApplication.sharedApplication().delegate as! AppDelegate).realm
// Access/Modify realm object
try! self.realm.write{
location.imageFile = fileName
}
Questions
1. Will this help reduce memory usage?
2. What are the drawbacks?
Interesting question
1.
In my opinion major drawback is that if you're going to use GCD with Realm. Remember that Realm is thread-safe so you can't use/modify Realm Object across threads/queues.
I handle Realm with Manager which is singleton. Maybe someone has better solution but this works just great.
class CouponManager: NSObject {
/// path for realm file
lazy private var realmURL: NSURL = {
let documentUrl = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask)[0]
let url = documentUrl.URLByAppendingPathComponent("coupons.realm")
return url
}()
lazy private var config:Realm.Configuration = {
return Realm.Configuration(
fileURL: self.realmURL,
inMemoryIdentifier: nil,
encryptionKey: "my65bitkey".dataUsingEncoding(NSUTF8StringEncoding),
readOnly: false,
schemaVersion: 1,
migrationBlock: nil,
deleteRealmIfMigrationNeeded: false,
objectTypes: nil)
}()
static let shared: CouponManager = CouponManager()
func save(coupons coupons:[Coupon]) {
let realm = try! Realm(configuration: config)
try! realm.write(){
realm.deleteAll()
realm.add(coupons)
}
}
func load() -> Results<Coupon> {
let realm = try! Realm(configuration: config)
return realm.objects(Coupon)
}
func deleteAll() {
let realm = try! Realm(configuration: config)
try! realm.write({
realm.deleteAll()
})
}
}
2.
You shouldn't worry about memory when use Realm. As TiM (he works in Realm) said in this answer and you should instantiate Realm every time you need to.
Questions
Will this help reduce memory usage?
From my experience, getting the shared instance of realm and setting the URL is not an heavy task. Yet opening and closing a transaction some times is (depends on how often you do that, but at the end of the day most clients do not write heavily enough).
So my answer for that is no, this will not help in memory usage.
And by the way, did you profile your app to check were does your memory usage go to? Thats a good place to start at.
What are the drawbacks?
I have been using realm for some time now, and I've written 2 apps in production with Realm . Now what I'm about to say is not about memory usage, but about design.
A. Realm as you know is a data base. And you should not just access it from any random place in the application (not from a viewController), especially without wapping it with some class of yours (UsersDataBase class for example), and I'll explain.
When objects in the DB start changing, you need to know who, were and from witch thread the writing is to DB.
One place were you can check and debug. And when I'ts all over your application I't is very hard to debug.
B. Realm is not Thread safe. That means that you really want to know on witch thread realm is getting written too. If not, you will end up getting thread executions, and believe be, this is not a fun thing to deal with. Especily when realm does not help with the stack trace some times.
C. You are accessing realm on AppDelegate with "UIApplication.sharedApplication()". This is not best practice since you can't access "UIApplication.sharedApplication()" from diffrent contexts, for example App Extensions. And when you will want to add App extensions to your app, you will end up reWriting any place your using it.

Resources