Realm linkingObjects array vs. Results - ios

I have a Book class and a ReadingSession class, with relationships declared like so:
public class Book: Object {
// (…)
var readingSessions: [ReadingSession] {
return linkingObjects(ReadingSession.self, forProperty: "book")
}
}
public class ReadingSession: Object {
// (…)
dynamic var book: Book?
}
Now I wanted to use Realm’s Results’ methods like filter() and sum() on that readingSessions property, but I can't, because it's a regular array.
So I added another computed property to my Book class:
var sessions: Results<ReadingSession> {
let realm = try! Realm()
return realm.objects(ReadingSession).filter("book == %#", self)
}
Now when I need to use those methods I go for the sessions porperty, and when I don't I use the readingSessions array.
So my question is: why is the first way, using the linkingObjects() method, recommended in Realm’s documentation? Is there a reason why I shouldn't completely replace that property with my latest one, using Realm’s Results? In my experience, working with Realm’s Results is usually a lot faster, besides the extra methods, and even if I need a regular array I can just convert the Results then. Is there any disadvantage to doing that?
Thanks in advance,
Daniel

For now, there is no better way than what you're currently doing. Using linkingObjects would be likely faster as the relationship can be navigated internally bidirectionally and the query doesn't need to be evaluated for the full dataset, so it also depends on the size of that. We plan to change that behavior as seen in issue #1508.

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.

Core Data naming convention for attribute accessors in NSManagedObject

I'm currently writing an NSManagedObject and am looking for naming convention ideas. Here's my code:
final class StrokeSample: NSManagedObject {
#NSManaged private var location: String
var _location: CGPoint {
get {
return NSCoder.cgPoint(for: location)
}
set {
location = NSCoder.string(for: newValue)
}
}
}
As you can see, I have a CGPoint object that is stored into Core Data as a String. The issue I have here, is how should I name those two variables that describe the exact same property in an elegant way?
What I've already considered:
In my xcdatamodel object, naming the attributes string[PropertyName]. Pros: resolves the ambiguity of, for example, having a property named rect that is described as String in the data model. Also, outside the data model, everything is very clear. Cons: Writing string before every attribute looks messy in the model.
Putting a _ before the computed properties' names in my NSManagedObject. This is what is highlighted in my example. Pros: This leaves the xcdatamodel clean. Cons: forces me to use underscores everywhere in my Swift code. 🤢
Putting a _ before the attribute name in the Data Model. Xcode prevents it.
I think option 1 is the better one since the mess is restricted at only one place. But if you have better ideas on this particular issue, they are welcome. Thank you 🙂
The convention seems to be to put an underscore after a property name when you want to make it "private" as in the data model it must start with a letter.

Value type design pattern to replace class

We are a looking for a value type design pattern in swift that will allow us to create a shopping cart to hold Products. We are currently using a class but that is a reference type and when we try to add two different version of the same product (i.e. with a different colors or sizes), the first item we added gets changed to the second item we added because it points to the same object in memory.
The design pattern needs to be “global” so we can access it from any page in the app. Right now this is our Cart class that stores all the items in the cart. What do we need to do to make this a value type or how does it need to be reengineered to use a struct without a class?
class Cart : NSObject {
var allProductsInCart = [MainProduct]()
override init() {
super.init()
}
class var sharedCart: Cart {
struct Static {
static let instance = Cart()
}
return Static.instance
}
}
The problem we are getting is that we need the products in the cart to be of custom class “MainProduct.” Right now as you can see, they are stored as “MainProduct.” Do we need to switch the products to a struct or other design pattern as well? How would we do that?
Yes, given the desired behavior between a value type vs. reference type you should use a Struct.
A commonly used "pattern" for doing this is called "Redux".
The idea is that you have one, immutable version of the "state" of your app.
It can be accessed from anywhere and the only way to update it is through "actions". These will reconstruct the entire state with the required updates.
ViewControllers and views, etc... subscribe to updates of various parts of the state.
So you could have an AppState that contains a ShoppingCartState. When a product is added to it your CartViewController will be informed of this update and can update its view etc...
There are many different frameworks that are built to use Redux so I won't recommend one as you should find the one that is right for you. But I believe this pattern best suits the usage you are after.

Swift - SharkORM ignore and encrypt property

I'm using SharkORM to create a SQLite database but I have the following question.
How can I encrypt and ignore a property in sharkORM?
class Example: SRKObject {
dynamic var birthdate : NSDate?
dynamic var age : NSNumber?
}
I'm trying to calculate the age from the birthdate, and I don't want to have a column in the table for the age.
Also, my data should be secure so I want to encrypt the birthdate, how can this be implemented?
Thanks for your support.
It appears that I might be wrong about ignoreEntities - that's not what you need. It appears that their documentation is not updated to reflect this but what you actually need is ignoredProperties :)
The actual Swift code you need to ignore a property on an object would look like this - I am using an example Person object to illustrate the code:
class Person: SRKObject {
dynamic var name : String?
dynamic var age : NSNumber?
dynamic var payrollNumber : NSNumber?
override class func ignoredProperties() -> [Any] {
return ["age"]
}
}
Since I have not worked with SharkORM before, I tested the code to make sure that the above does indeed work correctly :)
On the subject of the implementation for ignoredProperties, generally, the unit tests for a project (if they exist) are a good place to start to see how to use a certain method. But strangely enough, SharkORM does not seem to implement any tests to see if ignoredProperties works as it should. Hopefully, somebody from the development team sees this and fixes this oversight :)
With regards to encrypting a specific property, I believe all you need to do is implement encryptedPropertiesForClass. Since the implementation will be similar to the above one for ignoredProperties, I will leave the actual implementation to you :)
From the documentation:
In Objective-C properties need to be implemented using #dynamic, this is to indicate to the ORM that it will control the fetching and setting of these values from the database, and in Swift the property is implemented as var dynamic
So, if you don't want age to be a column in the database, don't mark it as dynamic. Since you want age to be a calculated property, you would have something like:
var age: Int? {
if let birthdate = birthdate {
return // whole years from birthdate to today
}
return nil
}

Why can't I use a relationship in a NSManagedObject subclass computed property? (CoreData, swift)

I'm using CoreData and I have a Book entity and a ReadingSession entity. Each Book has many ReadingSessions.
If I add this computed property to the Book class, it works:
var sessions: [ReadingSession] {
let request = NSFetchRequest(entityName: "ReadingSession")
let predicate = NSPredicate(format: "book = %#", self)
request.predicate = predicate
return try! DataController.sharedInstance.managedObjectContext.executeFetchRequest(request) as! [ReadingSession]
}
But if I add this one, it doesn't:
var sessions: [ReadingSession] {
return readingSession?.allObjects as! [ReadingSession]
}
This last example sometimes returns the correct array and sometimes just returns an empty array.
I tried the same thing with other relationships inside computed properties and the results are the same.
Why is that? Is there a reason why I shouldn't try doing this? Is my first example a valid workaround, or will it cause me problems later on? Should I give up using computed properties for this and just repeat the code when I need it?
Thanks in advance!
Daniel
Answering my own question, as Wain pointed out in the comments I should be able to use relationships inside computed properties, and my problem was actually somewhere else.
If you're interested in the details read the next paragraph, but, long story short, if you're having the same problem you should look into your relationships and make sure they're set properly as To One or To Many. Also check if you're setting all your properties in the right places and only when necessary.
I edited my question to remove lots of unnecessary details and make it more readable, but in the end the problem was that I had a User entity with a selectedBook property which was set when a user selected a row. I had set it up as a To Many relationship, but a user can have only one selectedBook at a time, so it should have been a To One relationship there. Also when I created a book I set user.selectedBook to it, but the selectedBook property should only be set when a user selected a book from a row. So I was setting and trying to access some of my relationships at all the right times. I tried to access a user.selectedBook before a user had even selected a row, for instance, and then it obviously returned nil, which messed up many other computed properties. Now I fixed all that and I'm able to access all my relationships from within computed properties without any issues.

Resources