Realm creation and population sequence - ios

I'm trying to build up a Realm of a bunch of data. This wouldn't be a problem but I've hit a wall - a gap in experience shall we say.
Creating one record is fine. However, one of the fields of that record is an array (<List>) of records from another table. Now my 2 questions are:
Does Realm support that? A list or array of Objects as one of the fields for a record... Answering no here will leed me on to an answer of my question - I will simply need to make an array of "primary keys" and query with those when I need to. If the answer is yes, proceed to question 2.
How would I go about creating those lists, bearing in mind that those tables might be created at a fraction of a second later than the current one, meaning those records don't yet exist and therefore can't be added to the list...
Example:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
var films = List<Film>()
}
See how all the film objects need to be created first before the character objects? Otherwise I could try add a film which does not yet exist and then it all crashes and burns :( Reason I want to try find a better way is, I'm dealing with 10 tables, a few hundred records and variable connection speeds. It would be too long to wait for each data retrieval to finish before the next one starts. AND since they are all suffering from the same problem (inter-connections), regardless of which I start with, it won't work...
Thank you :)

As discussed, for the object that haven't been created, you should create an empty object with only the primary key, then re-fetch and add value after the other network request called
For Many-to-many relationship, you can use Realm's Inverse Relationships to create linking between these objects like:
class Character: baseRLMObject {
var name: String!
var films = LinkingObjects(fromType: Film.self, property: "characters")
}

Like it's being discussed in the comments, you should be able to use Realm's inverse relationships feature to automate most of this process:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
let films = LinkingObjects(fromType: Film.self, property: "characters")
}
When calling character.films, a List will be returned of all of the Film objects whose characters property contains that object.
This is a lot easier and error-free than trying to maintain two separate relational lists between object types.

Related

Group results of filtering by date without dumping everything to memory

I have a list of Documents that are generated on multiple dates. I need to show a UITableView with the documents and each section has to be a different day.
I'm going to have several thousands of Documents saved on realm so I'm trying to find a way to group the Documents by creationDate without dumping the result of filtering the documents to memory.
Also it may happen that a new document is received so I would have to refresh the whole table and dump it to memory again just to add one document.
I couldn't find any way of doing this. I also think of paginating the results so I have an smaller list to "handle" in memory but Realm can't paginate either so I'm not sure how to proceed...
My Document looks like this:
final class Document: Object {
#objc dynamic var createdByUserId: String?
#objc dynamic var creationDate: Date?
#objc dynamic var userId: String?
}
I've solved this before:
Method 1 [Very slow but least maintenance]:
During runtime, create an array containing unique startOfDay dates for all your creationDates of your Document Objects. Call this array DateGroups.
In every document object, add 'dateGroup' variable.
final class Document: Object {
#objc dynamic var createdByUserId: String?
#objc dynamic var creationDate: Date?
#objc dynamic var dateGroup: Date?
#objc dynamic var userId: String?
}
Now you can pull out objects from a specific dateGroup:
realm.objects(Document.self).filter("dateGroup = %#", Particular-Date-Group)
Method 2 [Very fast but requires high maintenance]:
You declare everything like in method 1.
However, you have to create a Realm Object that maintains active dateGroups. Call it DateGroup and let it have only one variable called date or startOfDay.
Method 2 is faster than Method 1 but requires high maintenance because you have to keep track of when a dateGroup should be added or deleted

Manipulate Relationships in Core Data

I have a NSManagedObject subclass defined like this:
extension Item {
#NSManaged var name: String
#NSManaged var parent: Item
#NSManaged var children: [Item]
}
In order to set the 'parent' attribute, I use this:
anItem.parent = aParent
And I know it will automatically append anItem to aParent.children too.
However, I would like the child item to be added at a specific position.
This idea is to have a sorted array of children.
I know I could simply do this:
aParent.childen.insert(anItem, atIndex: specificPosition)
But is there any method to override or operator to overload in order to automatically achieve that ?
Inserting the child at a particular position will not ensure the same sort order later when you fetch.
You will need an additional attribute to define the sort order during the fetch.
Have a look at this answer
A couple misconceptions here:
CoreData to-many relationships are not of type Array, they are Set or NSSet. This can be verified by having your subclass generated by Xcode.
It is possible to to have an ordered relationship - just take a look at the relationship inspector in your Core Data Model. This will change it to a NSOrderedSet
MAJOR CAVEAT to (2) - The order is determined exclusively by the order that you add them - see this link for more info.
It is much more memory intensive to store these as ordered, if you can, have an attribute you can use to order them after fetching.

Will CoreData duplicate objects?

I have an NSManagedObject called "Routine," that has already been saved to my data model. It has a to-many relationship to another NSManagedObject called "Workout". I want to edit the Routine in order to add more workout relationships to it to it.
let routine = fetchedResultsController.objectAtIndexPath(indexPath) as! Routine
The ViewController where I edit the Routine in my data model contains an array of Workout objects:
var myWorkouts = [Workout]()
Some of the workouts in the "myWorkouts" array may have been already associated with Routine, whereas others may not have (a new workout). I create a relationship between each workout and the routine like this:
for workout in myWorkouts {
routine!.setValue(NSSet(objects: workout), forKey: "workout")
}
My Question: If a relationship between a Routine and a Workout has already been created, will the above for-loop create a duplicate of that workout to associate with my routine, or will it only create new relationships for previously unassociated workouts?
I hope my questions makes sense. Thanks in advance for the help!
Routine CoreDataProperties File
import Foundation
import CoreData
extension Routine {
#NSManaged var name: String?
#NSManaged var workout: Set<Workout>?
}
So, you're working with Sets, which means that they only always contain each value once. Therefore, regardless of the enclosing objects (in this case NSManagedObjects), there will only be one in there. You're good - re-stating the relationship won't change anything.
I might suggest, however, that you can do a couple of things to make your life easier:
If you haven't already, create the concrete subclasses using Xcode's built in tools, so you can directly access relationships and properties.
In the concrete subclasses +NSManagedObjectProperties file, redefine those to-many relationships from NSSets? to Set<MyClass>?. This allows you to call Swift-native functions, and works correctly, as Set is bridged from NSSet.
In the future, just call routine.workout = workout, which is much clearer than the way your code defines setting the relationship.

Insert objects into NSSet's with multiple many-to-many relationships in Swift

Let's say I have a sports app with 2 Entities. (this is purely an example)
Teams and Players.
class Team:NSManagedObject{
#NSManaged var name:String
#NSManaged var players:NSSet
}
class Player:NSManagedObject{
#NSManaged var firstName:String
#NSManaged var lastName:String
#NSManaged var playsFor:NSSet
}
Each Team has a set of players.
Each player has a set of teams they play for.
So in essence, I have 2 many-to-many relationships. Had there only been one, I could use the inverse to insert the object into its set without any trouble.
Because of this sad dual many-to-many structure of these object, I find myself in a pickle. Now before someone says I should take an intermediate object and do a one-to-many with that object to remove the second object, I would LOVE to do that, but, in this case, I can't, so I'd like to open the topic of how to solve this particular problem.
I've tried two different attempts.
The biggest issue is that the type of players is an NSSet, I can't just do an addObject because NSSet is immutable.
My first idea was the following:
func addPlayer(player: Player) {
var allPlayers = self.players.allObjects
allPlayers.append(player)
self.players = NSSet(array: allPlayers)
}
Now this is bad on multiple levels. So I'll just move on right away.
My next idea was that clearly it was bad to be taking a NSSet to an Array to a Set again just to add one item, so I changed the data model of the Team to be
#NSManagedObject var players:Set<Player>
Then my method was simplified to:
func addPlayer(player: Player) {
self.players.addObject(player)
}
I really liked this, until I turned on the profiler and my execution time had actually become SIGNIFICANTLY worse.
So my next attempt was the following, clearly leaving the types of the managedobjects as NSSet gives you some significant benefits, so I didn't mess with that anymore.
func addPlayer(player: Players) {
let allPlayers = NSMutableSet(set: self.players)
allPlayers.addObject(player)
self.players = allPlayers
}
I got a small speed bump off of the last change but am still not happy with it. What other optimizations are possible? Any ideas?
In reality, I am working with an application using a VERY old database with hundreds of thousands of objects that we have no control over refactoring. I'm trying to do optimizations without completely reworking the data-model and destroying a front end. Any help advice or ideas would be appreciated.

Class or Struct for a model similar to a relational database?

The application in question is built as follows:
A user selects a job
the job can have many components
each component can have many lineitems.
I am not clear on how this should be structured - should this be class or structs? Seeing that only one job is being processed at a time, I am fairly confident that jobs should be a class. However, when there are multiples of a certain object type, I am not exactly clear on how to form them, like the components and lineitem objects.
The application consists of ViewControllers and TableViewControllers. All the data is fetched from a server in JSON and populated into the appropriate view as needed. Here are the object types as they are currently setup:
A job object:
// Job Object
//
public struct Job {
static var globalId : String?
static var name : String?
static var status : String?
static var client = Client()
static var components = Array<Component>()
// etc..
}
A Component like so:
// JobComponent Object
//
public struct Component {
var name:String? = ""
var fmRecordId : String?
var startTS:NSDate?
var endTS:NSDate?
var notes:String? = ""
var items = Array<Lineitem>()
// etc...
}
and finally, a lineitem:
// Lineitem Object
//
public struct Lineitem {
var fmRecordId = String()
var itemName = String()
var itemNumber = String()
// etc...
}
All of these object are built within a public class called "PL".
When a user selects lineitem and edits it's values, the values are not available outside the VC in which they are edited because the VC isn't referencing the lineitem that is was passed, it is simply copying it. The same happens with components.
A workaround I found was to use the the Job struct PL.Job.self and always modify the components and lineitems like so where i = a desired index in the array:
PL.Job.components[i] to access a component
PL.Job.components[i].items[i] to access a specific item within that component.
However, this doesn't scale very well.
The desired behavior is to be able to pass a reference to a particular instance of an object around rather than pass around the index path of those objects in the PL.Job object.
I am well aware there is something wrong with how this is currently structured, but could someone please point me in the right direction?
A couple of points:
You can only pass class instances by reference. If you want to be able to pass a reference to a particular LineItem or Component or Job, and you want to be able to make changes to that object that are effective everywhere, then you need to define them as classes and not structs. Instances of struct types are always passed by value and not be reference. And when a value type is passed, it is copied, meaning that you create an entirely new copy of the object, and mutating the copy has no effect on the original.
Your Job struct only has static properties - i.e., there will only ever be one globalId, name, status etc. throughout your entire application. If you want to have multiple instances of Job, then these should not be static properties. You say that only one Job will be processed at a time, so maybe that was intentional. Either way, it is still often preferable to create an instance of a Job class that has those properties. It certainly would give you more flexibility later if you decide to make it possible to hold references to multiple jobs in memory, or to allow the user to select between different jobs, or switch between jobs, etc. For example, you may want to allow a user to switch to the Job they were processing earlier without necessarily destroying the Job that they are working on now.
But I think the main point is that you will need to define your objects as classes if you want to be able to pass them by reference. If you modify an object that is passed by reference, all other references to the same object will show the same changes (because, after all, they are just references to the same object). That doesn't work with value types, like structs.

Resources