Persists One RLMObject - ios

In my app I have a CustomUser which inherits from RLMObject.
I am wondering how do I store 1 object only for this CustomUser object?
func saveUser() {
let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
realm.addObject(customUser)
realm.commitWriteTransaction()
}
This code will write an additional object to the database everytime it runs. Resulting in many objects in database after the code is run multiple times.
Any thoughts please?

The simplest way would be to query Realm to check if there are any previously saved objects before going ahead and adding a new one.
func saveUser() {
let realm = RLMRealm.defaultRealm()
if CustomUser.allObjects().count > 0 {
return
}
realm.beginWriteTransaction()
realm.addObject(customUser)
realm.commitWriteTransaction()
}
On a more advanced note, if you want to reference your single CustomUser object explicitly, you can use Realm's primary key feature to directly query for that specific object. There's more information on Realm's website on how to set up classes to use primary keys (In the Objective-C section, since it looks like you're using the Objective-C bridged version).
Good luck!

Related

Can I make realm.writes directly in the Object data model?

I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.

best way to rename a class in Realm swift

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?
Here's the problem: I have a Realm class named Collection
Collection is also the name of a standard Swift protocol.
While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.
I read about this notation that can be used like this:
#objc(SpecialCollection)
class Collection: Realm.Object {
let items: List<ItemObject>
let name: String
let url: String
....
}
So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.
This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.
All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:
migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
migration.create("SpecialCollection", value: oldObject)
}
But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).
So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.
Blockquote
I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.
The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.
Thanks for any help in this. It is greatly appreciated.
I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.
So here's how I did it:
Migrate your Collection class first, creating SpecialCollection.
While migrating, walk the Collection's list and create new
SpecialItemObject for each ItemObject and append it to the new list.
Delete each ItemObject.
Now enumerate all ItemObject remaining
in the realm and create a new SpecialItemObject and map its values
over. The reason is there may be other ItemObject floating around
in your realm, not tied to the list.
Delete all remaining ItemObject.
migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
let specialCollection = migration.create(SpecialCollection.className())
specialCollection["name"] = oldObject!["name"]
specialCollection["url"] = oldObject!["url"]
if let oldItems = oldObject!["items"] as? List<MigrationObject>,
let newItems = specialCollection["items"] as? List<MigrationObject>
{
for oldItem in oldItems
{
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"] // You didn't specify what was in your ItemObject so this example just assumes a name property.
newItems.append(newItem)
migration.delete(oldItem)
}
}
}
migration.deleteData(forType: "Collection")
// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"]
}
// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject")
So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.
Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!
Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

Best way to store a single instance of RLMObject

I'm currently developing an iOS app with Realm as the backend data storage. I have a class that is an RLMObject for the user profile. It stores their name, profile picture, stats, etc.
There should only always be one of these objects, however I know implementing a singleton pattern is usually a bad idea. Currently I have it implemented as
//AppDelegate.swift, applicationDidFinishLaunching
//If there's no user profiles yet (first time launch), create one
if UserProfile.allObjects().count == 0 {
let realm = RLMRealm.defaultRealm()
try! realm.transactionWithBlock() {
realm.addObject(UserProfile())
}
}
//ProfileViewController.swift
//Get the first UserProfile
var userProfile: UserProfile? {
get {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}
}
Is there a better way to keep track of a single instance of this class?
Your code sample uses a computed property, which will fetch the object from the Realm each time you access it.
Instead, try using a lazy var property:
lazy var userProfile: UserProfile? = {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}()
This type of property will load the object from the Realm only the first time it is accessed. All subsequent accesses will be directly to the object.
Note that, since UserProfile is a Realm object, its fields will automatically update in response to changes made to the underlying object in the Realm. Likewise, any changes you wish to make will need to be wrapped within a Realm write transaction.
In terms of your overall architecture, there is nothing wrong with storing a single instance of an object type in a Realm similar to what you are doing. You may want to give your UserProfile object a hardcoded constant primary key, then use the 'add or update' version of the update API (see https://realm.io/docs/swift/latest/#updating-objects). This will allow you to avoid having to explicitly create a new object.

Is it possible to make copy of Realm object?

I want to refresh the Realm database in the background thread like this:
(Because I have got fresh data from Webservice)
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm deleteAllObjects]; // !!
[Pubs createOrUpdateInRealm:[RLMRealm defaultRealm] withJSONArray:data];
[realm commitWriteTransaction];
Problem is, that meanwhile I delete & renew the objects in Realm db, user can open some Detail ViewController pointing to some Realm object (Pubs) which has been deleted meanwhile so the exception is thrown.
I don't see any solution for this, except always when I would like to access the Realm object from Detail controller or its property I would need to always do something like this:
(That means always get Realm object, but that can probably fail too)
pub = [Pubs objectsWhere:[NSString stringWithFormat: #"pubId = %lu", (long)_selectedPubId]].firstObject;
But I am not using this solution. I am thinking best would be if I could call in Detail view controller something like this:
pub = [Pubs objectsWhere:[NSString stringWithFormat: #"pubId = %lu", (long)_selectedPubId]].firstObject;
pub = [pub safeCopy];
So the PubRealmObject can be meanwhile deleted, but the pub object will solo exist in the memory (only for the purpose to access its data properties).
So did someone try something similar?
Or maybe even using some iOS SDK way like this?
I need to only access the data properties as I say, not operate with realm object methods like delete or update the object in the db.
Btw I tried to call the update of Realm db in the main thread, but the problem is it takes like 5-7 seconds (only 1000 JSON objects) so it lags the application. That's why I am thinking the background update & safe copying of object could be better.
But I am thinking that it can fail even while copying the object, so what is the solution for this? (background update vs safe access of Realm object)
It's usually not a good design pattern to have a view controller relying on a data model that can be deleted out from underneath it. It's possible to check if a Realm object has been deleted to avoid exceptions by checking its object.invalidated property.
In any case, to create a detached copy of a Realm object, all you need to do is:
RLMObject *copiedObject = [[RLMObject alloc] initWithValue:object];
This will make a copy of the object, but it will not be inserted into any Realm instance. Please note that if the object links to any other Realm objects, these will not be copied as well; the new object will just be pointing at the existing copies.
But I still feel like I need to mention that you could probably just make your implementation of updating Realm from your web service a bit smarter to avoid the need to do this.
If your objects implement a primary key, then when you call createOrUpdateInRealm, the existing objects will be updated with the new values.
Good luck!
With Swift:
Previously answered here
As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Note")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.

How to create a database using Parse that will work offline?

My app is a directory with basic things like phone numbers and locations. There's going to be many entries and I will have to update the database on a weekly basis and I don't want to rely on my users to update their app using the App store, so I want the data to be updated whenever they connect to the internet, but I want all of the data stored locally so they can use it even if they don't have internet access. For my first app, it's proving to be quite tricky :)
I think I'll have to use Parse so I can update the database whenever I need to, along with something like Realm (https://realm.io/) or Core Data (hopefully not Core Data :( ). I read about Parse's Local DataStore, and if there's a way to make it work for my needs I'd definitely use it. I like the simplicity of Realm though and if I there's a way to make it work with Parse, that would be the route I'd want to take.
Can somebody show me an example on how they might go about doing this? If the PFObject or RLMObject is called person and has two strings (phone number and name, how would you get that from Parse.com to the device (local storage)?
This is an extra and I don't even know if it's possible*
While the app is downloading from the App Store, could it download the data from Parse? As in, the second the user opens the app, even if they no longer have internet access, the data is locally stored and usable to them.
(I only know Swift but may be able to understand obj-c if anybody cares to show me some code snippets)
Any help at all would be greatly appreciated :)
Joe from Realm here.
First to retreive an object from Parse you will need to do this:
var query = PFQuery(className:"GameScore")
query.whereKey("playerName", equalTo:"Sean Plott")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
println(object.objectId)
}
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
You can read more about this here
Once you retrieve your objects, I would loop through them as you see here
for object in objects {
println(object.objectId)
}
And then I would use Realm's Realm().create(_:value:update:)(shown in code below). You need to make sure you have a primary key to use this though (I would use parse's objectId as the primary key in Realm). Here is how you set a primary key in Realm.
An example of how I would import them into Realm would be something like this (Where Venue is the class of the way the object is stored in Realm):
let realm = Realm()
realm.write {
for object in objects {
realm.create(Venue.self, value: object, update: true)
}
}
You can read more about importing here
Lastly the logic of when you do this is up to you. You could do it each time the person opens the app. One thing with syncing local databases with the ones on the server is that it's good to just check another Table maybe called LastUpdated. This table will let you know if you need to update your local database. Overall it's a very manual process but it depends on the situation and how you want to structure your app.

Resources