Realm duplicate Objects - ios

In my CoreData version I could write
let doubledContacts = contacts + contacts
where contacts was of type [NSManagedObject]. In Realm this forces a crash: "Can't mutate a persisted array outside of a write transaction."
I don't want to persist doubledContacts so I don't need a write transaction. All I want is a new collection where every contact is contained twice.
How do I solve this in Realm?

The easiest solution is to pull all contacts in an array:
let contactsArray = contacts.map { $0 }
let doubledContacts = contactsArray + contactsArray
But note: that will set lazy semantics of List (or Results) out of effect and cause that you pull all object instances into mapped memory.

Related

Why do we need to call context.delete to delete an item from NSManagedObject array?

Suppose I have;
var itemArray = [Item]()
and Item is a NSManagedObject. Item has two attributes "Title":String and "Done":Boolean. Here is the picture of my data model.
// Item+CoreDataClass.swift
// This file was automatically generated and should not be edited.
//
import Foundation
import CoreData
public class Item: NSManagedObject {
}
When I change the value of Done and call context.save, it is automatically reflected to Persistent Container. However, when I remove an element from array by saying,
itemArray.remove(at: someindex)
and call context.save. The item is not deleted from Persistent Container. Only if I called,
context.delete(itemArray[someindex])
then the item is truly deleted from store.
So why only removing from itemArray and save context is not sufficient although changing an attribute' value and save context is sufficient for successful CRUD operation on Core Data?
When you change an attribute on an Item object then Core Data (actually the NSManagedObjectContext) detects that since the Item belongs to the NSManagedObjectContext and the item is marked as dirty. But your array has no connection to the NSManagedObjectContext in any way so any changes you make it to remains undetected by the NSManagedObjectContext and that is why you need to tell it explicitly that you want to delete the item you removed from the array.
Another way to look at it is that anything you create/define in your Core Data model is known by NSManagedObjectContext but anything created outside in swift code is unknown. If you start working with to-many relationships between entities you will see that then adding or removing objects from the to-many collection will be handled directly by the NSManagedObjectContext in a way you expected for your array.
The array var itemArray = [Item]() has no direct relation with the underlying database. Therefore removing items from that array doesn't affect the Core Data database.
To create, save or delete NSManagedObject instances in a Core Data database you need to call the related functions of a valid NSManagedObjectContext.
Any operation on CoreData should be done through NSManagedObjectContext as it is the scratchpad to access or update any entity in database. So in your case while deleting the Item entity, you should do that through context only to get reflected on database.
var itemArray = [Item]()
let context = //get your context
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
let predicate = NSPredicate(format: " (primaryKey == %#) ", "your_primary_key")
fetchRequest.predicate = predicate
itemArray = try! context.fetch(fetchRequest)
for i in 0 ..< itemArray.count where i < itemArray.count-1 {
context.delete(itemArray[i])
}
do {
try context.save()
} catch {
// print the error
}

How to create initial Realm objects that get added upon installation of app

Say I am creating an object that takes two strings and acts like a dictionary.
class WordInDictionary: Object {
#objc dynamic var word: String = ""
#objc dynamic var meaning: String = ""
What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?
Also, is there a way to make it so that just those initial objects can't be deleted?
"What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?"
One option would be to have some code near the realm initialisation that checks if there are any WordInDictionary objects already in the realm - if not then add the required default objects.
E.g.
let realm = try! Realm()
if realm.objects(WordInDictionary.self).isEmpty
{
// Add required words here
}
"Also, is there a way to make it so that just those initial objects can't be deleted?"
I don't know of a way to make realm objects read-only. You'd have to implement this in code in some way, e.g. have a isDeletable boolean member which is true for every user-created object and false for your default members, then only delete those from realm.
E.g. for your deletion code:
func deleteWords(wordsToDelete: Results<WordInDictionary>)
{
try! realm.write
{
realm.delete(wordsToDelete.filter("isDeletable = true")
}
}

Realm: live updates of constant values

I'm using SwiftRealm 2.03 and do not understand the magic how constant data (even metadata) gets updated if the data in realm changes...
Here an example:
private func closePastExistingTravelTimes(){
let travelTimes = fetchTravelTimes(onlyNotClosedTravelTimes: true)
guard travelTimes.count > 1 else {
return
}
let numberOfTravelTimes = travelTimes.count
for index in 0..<numberOfTravelTimes-2{
print("index:\(index) count:\(travelTimes.count)")
try! realm.write {
let travelTime = travelTimes[index]
travelTime.travelPhaseIsClosed = true
realm.add(travelTime, update: true)
}
}
}
I'm loading data in the beginning and store them in an constant.
Then I iterate over the items and change the condition of the query so that the fetched data would change if I would query again. But I don't. What even is more suprising that the constant numberOfTravelTimes is even adjusted as you can see below in the log.
index:0 count:5
index:1 count:4
index:2 count:3
index:3 count:2 --> BAM - Exception
What is happening here? How can I be save in my example?
Realm Results objects are live, meaning if you update an object in such a way that it no longer conforms to a Results query, the Results object will update to exclude it. This means you need to be careful when you base a for loop off a Results object, since if the Results object mutates in the middle of the loop, you'll end up with an exception.
Normally, the easiest, but not the most elegant way to mitigate this is to copy all of the Realm objects in a Results object to a static array so it won't mutate during the loop.
In this particular case however, it would be more appropriate to just enclose the entire for loop inside the Realm write transaction. This is generally best practice (Since it's best to batch as many Realm writes into as few write transactions as possible), but in this case, it will have the added advantage of not updating the contents of the Results object until after you're done with it.
try! realm.write { // Open the Realm write transaction outside of the loop
for index in 0..<numberOfTravelTimes-2 {
print("index:\(index) count:\(travelTimes.count)")
let travelTime = travelTimes[index]
travelTime.travelPhaseIsClosed = true
realm.add(travelTime, update: true)
}
}
You should iterate your results in reverse order.
for index in (0 ..<numberOfTravelTimes-1).reverse()
See if it helps

resetting a Swift array causes strong references?

I am getting many low memory warnings, and ultimately a crash in my iOS app.
I have some reason to believe that it has to do with strong/unknown references contained in the data model for a UICollectionTable, which is a non-optional Array of non-optional Objects of type BrowsableRecipe. I initialize the array as follows:
var recipes = [BrowsableRecipe]()
where data is a list of BrowsableRecipes returned from an async call from the server.
ServerMessenger.sharedInstance.getRecipesForHomePage(recipeIndex, pageSize: pageSize){ responseObject, error in
if let data = responseObject{
//TODO confirm first recipe object doesn't have error param set!
self.recipes = data
self.recipeIndex = self.recipeIndex + 1
dispatch_async(dispatch_get_main_queue()) {
self.collectionView!.reloadData()
}
}
Where I think the trouble lies is that when I reset the array self.recipes to a new list of data, I think that the old array of objects is somehow still maintained or that the points to each BrowsableRecipe are somehow still extant in memory. I think it happens here:
self.recipes = data
where this is happening after self.recipes has been set, and data refers to a completely different list of BrowsableRecipes. Do I need to go through item in the array and set each BrowsableRecipe to nil? Also that may result in an error since BrowsableRecipe is not an optional.
How about first "emptying" the array like so:
self.recipes = []
or
self.recipes.removeAll
before self.recipes = data?

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Resources