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.
Related
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
I need to Display and modify my data structure from both Apple Watch and iPhone.
The Database:
I am currently using a simple Realm Structure where I have an Object A and an Object B which can hold lots of A's.
So on iPhone the user can create a B and add A's and view of course all A's and B's.
I want the Apple watch to show all A's of the current B and give the users the chance to add new A's to their current B.
The way I have tried to do it:
I wanted to move the hole Realm file from iPhone to the watch or the other way. (That was a tip from the Internet)
iPhone Code:
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() { //makes sure it's not an iPad or iPod
let watchSession = WCSession.default()
watchSession.delegate = self
watchSession.activate()
transferRealmFile()
if watchSession.isWatchAppInstalled {
do {
try watchSession.updateApplicationContext(["foo": "bar"])
} catch let error as NSError {
print(error.description)
}
}
}
}
func transferRealmFile(){
if let path = Realm.Configuration().fileURL {
WCSession.default().transferFile(path, metadata: nil)
}
}
WathcKit Extension:
func session(_ session: WCSession, didReceive file: WCSessionFile) {
//set the recieved file to default Realm file
var config = Realm.Configuration()
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
let realmURL = documentsDirectory.appendingPathComponent("data.realm")
if FileManager.default.fileExists(atPath: realmURL.path){
try! FileManager.default.removeItem(at: realmURL)
}
try! FileManager.default.copyItem(at: file.fileURL, to: realmURL)
config.fileURL = realmURL
Realm.Configuration.defaultConfiguration = config
}
Then I call transferRealmFile() every time I write to Realm. This works but I can't solve this Problems:
Problems:
It doesn't work if only watchKit App is started.
Apple Watch to iPhone doesn't work the same way. (I think I need to change the didRecived code, but I don't know what)
Question:
Do you know who to solve this 2 Problems or do you maybe know a better way to handle the situation or will the way we interact between iPhone an Watch change in WathcOS 3?
With watchOS1 it was possible to use AppGroups to share resources (even your Realm database) between an iOS app and its Watch extension. However, Apple removed this in watchOS 2, so now the only way to share data between your iOS and watchOS apps is via WatchConnectivity. Have a look at this answer.
Sadly the WatchConnectivity framework requires the WCSession to be active on both devices for transferring data, so you can't really get around problem 1.
In my opinion it is a better solution to only communicate the changes between the two apps and not send the whole Realm file, since your Realm file can get quite big and hence sending it forward and backward can take a lot of time and resources, while just sending the changes should be way faster.
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)")
I've got some objects in my Realm database. I need to delete an object from it.
When I delete my object from my Realm() instance, the objects are well deleted.
But after performing the delete request, I need to retrieve all objects from the database, but here surprise, the object is still here.
I think I have a thread problem, or something like that.
I don't know where to investigate.
My simplified code :
My delete method :
func myDeleteFunc(completion : ()->Void){
let realm = try! Realm()
// ...
try! realm.write {
realm.delete(myObject)
}
completion()
}
// Here the object have been deleted from the realm instance, no problem
This method is called from a viewController where I execute the completion block.
This completion block contains the request that retrieve all objects from my Realm database :
The viewController that executes the method and the completion block :
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
I think my 2 realm instances differs, or have problems between threads because I have a DispatchQueue.main.async.
Any ideas ?
EDIT :
I noticed that when I check with breakpoints, sometimes it works.
So maybe that the delete request have not been committed yet, and that I retrieve the objects before the end of the delete request ?
Make sure you put the async block in an autorelease pool:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
Should be
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
}
Make sure you do this autoreleasepool { ... } wrap for any background thread where you create a Realm instance, primarily in the GCD.
If that still doesn't work, you can do:
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
Realm isolates transactions on each thread to avoid changes from one thread immediately affect another. This mechanism also avoids the "faults" inherent to ORMs.
In your code, you can choose to refresh (advance) the realm to the latest state at points that you control and can handle data before and after the refresh being different.
Realms on a thread with a runloop (such as the main thread) auto-advance on every iteration of the runloop by default.
In your code sample, you invoke DispatchQueue.main.async immediately after a commit from another thread, which means that if you already have a Realm on the main thread, the async block will be at the same state and won't include the last commit.
But you can call Realm.refresh() explicitly at the start of your async block, which will ensure that this block sees that last commit:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
realm.refresh()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// ...
}
}
See Realm's docs on Seeing Changes From Other Threads for more information.
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.