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

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

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

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)")

Different Realm Configurations Appearing in Swift App

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.

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.

Realm Threading Confusion

So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.

Resources