Multiple Realms architecture - ios

I have a question regarding my architecture of multiple Realms in my project. Consider the following model:
#objcMembers class Book: Object {
dynamic var bookID = UUID().uuidString
dynamic var title: String = ""
dynamic var year: Int = 0
override static func primaryKey() -> String {
return "bookID"
}
}
What I would like to do is to be able to either like a book or to put it into a shopping list.
The first solution I came up with was to have three default Realms for:
Books - RealmBooks
Books liked - RealmLiked
Books added to a shopping list - RealmShopping
With this fairly simple approach I was forced to have several Realms. In the future I would like to sync the data from the above Realms to the Realm Cloud. So to avoid this is it possible to have an architecture that combines the data of let's say RealmLiked and RealmShopping? This could be of an object type UserSettings:
#objcMembers class UserSettings: Object {
dynamic var userID = UUID().uuidString
let likedBooks = List<Book>()
let shoppingListBooks = List<Book>()
override static func primaryKey() -> String {
return "userID"
}
}
Then I would simply sync UserSettings and this would act like the master Realm. Obviously the remaining RealmBooks would be synced in a separate Realm. The reasons for all of this is due to trying to keep my project simple as well as the limitation of having Up to 3 Realm Instances synced in the standard version of Realm Platform Cloud.
I would like to know the best practice in this scenario. Any help is much appreciated.

Related

Can't save data from 2 custom classes with userDefaults

In my app I have 2 custom classes (super class and subclass) and based on them data is created and deleted from the user dynamically and I want to be able to save the data permanently with UserDefaults and I'm not sure how.I tried looking the answers like this one - Saving custom SWIFT class with NSCoding to UserDefaults and I fint it hard to understand.
My first class(super class):
class Day {
var dayName: String
var subjects: [Subject]?
init(dayName: String) {
self.dayName = dayName
}
}
My second class(subclass):
class Subject: Day {
var subjectName: String
var startsAt: String?
init(dayName: String,subjectName: String) {
self.subjectName = subjectName
super.init(dayName: dayName)
}
}
Thanks for helping me.

How would one create a List<AnyRealmObject> in Swift?

OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.

Add arrays to Realm with swift 3

I'm new in Realm and I tried to add an Array as I did with strings and I ended up with some errors. So after a little search I found out a solution:
class Sensors : Object {
dynamic var name = ""
dynamic var message = ""
var topic: [String] {
get {
return _backingNickNames.map { $0.stringValue }
}
set {
_backingNickNames.removeAll()
_backingNickNames.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
let _backingNickNames = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["topic"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
This is working very good, now I want to add another array inside this class.
If someone knows any other ways to add arrays with realm please share it.
Thanks in advance
As a general rule it's way more efficient to use the one-to-many relationships provided by Realm instead of trying to emulate them by using arrays (Realm's collections are lazy, the objects contained are instantiated only when needed as opposed to plain Swift arrays).
In your case, if I understand correctly what you're trying to do, you want to add [RealmString] Swift arrays to the _backingNickNames list.
Why not use the append(objectsIn:) method of Realm's List class (see here), like this:
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person?
}
// Person model
class Person: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
let dogs = List<Dog>()
}
let jim = Person()
let dog1 = Dog()
let dog2 = Dog()
// here is where the magic happens
jim.dogs.append(objectsIn: [dog1, dog2])
If you want to do the opposite (convert from a List to an Array) just do :
let dogsArray = Array(jim.dogs)
• • • • • • • •
Back to your own posted solution, you could easily refactor the model to accommodate this. Each Sensor object could have several Topic and several Message objects attached.
Just ditch the message and topic computed properties and rename topicV and messageV to topics and messages respectively. Also rename RealmString to Topic and RealmString1 to Message.
Now, you could easily iterate through the, say, topics attached to a sensor like this :
for topic in sensor1.topics { ... }
Or if you want to attach a message to a sensor you could do something like this (don't forget to properly add the newly created object to the DB first):
let message1 = Message()
message1.stringValue = "Some text"
sensor2.messages.append(message1)
So, no need to use intermediary Swift Arrays.
After testing I managed to add another array like that:
class Sensors : Object {
dynamic var type = ""
dynamic var name = ""
dynamic var badge = 0
var topic: [String] {
get {
return topicV.map { $0.stringValue }
}
set {
topicV.removeAll()
topicV.append(objectsIn: newValue.map({ RealmString(value: [$0]) }))
}
}
var message: [String] {
get {
return messageV.map { $0.stringValue1 }
}
set {
messageV.removeAll()
messageV.append(objectsIn: newValue.map({ RealmString1(value: [$0]) }))
}
}
let topicV = List<RealmString>()
let messageV = List<RealmString1>()
override static func ignoredProperties() -> [String] {
return ["topic", "message"]
}
}
class RealmString: Object {
dynamic var stringValue = ""
}
class RealmString1: Object {
dynamic var stringValue1 = ""
}
What bogdanf has said, and the way you've implemented it are both correct.
Basic value types aside, Realm can only store references to singular Realm Object objects, as well as arrays of Objects using the List type. As such, if you want to save an array of types, it's necessary to encapsulate any basic types you want to save (like a String here) in a convenience Realm Object.
Like bogdanf said, it's not recommended to convert Realm Lists to standard Swift arrays and back again, since you lose the advantages of Realm's lazy-loading features (which can cause both performance and memory issues), but memory issues can at least be mitigated by enclosing the code copying data out of Realm in an #autoreleasepool block.
class MyObject: Object {
dynamic var childObject: MyObject?
let objectList = List<MyObject>()
}
So in review, it's best practice to work directly with Realm List objects whenever possible, and to use #autoreleasepool any time you do actually want to loop through every child object in a Realm. :)

CRUD methods in custom Swift classes

I'm wondering if it's a good idea to include CRUD methods inside custom Swift classes, or are they better off in a separate class?
For example I have a class called User.swift:
class User {
var firstName: String
var lastName: String
var id: int
}
Now, would it be okay to include the get and create methods here? These methods will make API calls via Alamofire:
class User {
var firstName: String
var lastName: String
var id: int
static func add(user: User) -> User {
let parameters = ["firstName": user.FirstName , "lastName": user.LastName]
return sendRequest(.POST, url: "example.com/users", parameters: parameters)
}
static func getById(userId: Int) -> User {
return sendRequest(.GET, url: "example.com/users/\(userId)")
}
}
Should these methods be in a separate class, like in an ApiHelper class?
My application passes around the User object in arrays and dictionaries in several places, so wondering if it's good to keep it clean with just the properties.
I think better declare such methods in some ApiHelper/Router singletone class, as well as they must work async, work with some parse system (RestKit probably) and return fetched objects via closures with some delay
The different opinions about the right approach often causes a heated discussion. And this also extends to questions whether we should perform validation in the model class (User) or in the controller, how we should handle versions, serialisation, errors, undo/redo, locking, asynchronicity, etc., etc.
And the resulting code should still be clean, comprehensible, extensible, testable and put into a library so that we can reuse it in other projects!
IMHO, there's no right approach. IMHO, I would start with the following principles:
Your User class is seen in your solution as an Entity. An entity has a property ID and possibly other principal methods, for example, it exposes an init which takes a dictionary of attributes where the class/struct instance can be initialised.
That entity also knows about a "Persistent Store", so your User class may also conform to a protocol "Storable", which exposes class and instance methods like save, create, update, delete, query, etc.
This is only the tip of an iceberg what comprises a complete solution. You might look how others have solved this problem. For ideas, see
Use Core Data objects which you populate from the JSON
Implement the Active Record Pattern
Look at some Object Relational Mappers (there are a lot of implementations and libraries). Even though, you need to map from JSON to Objects, these gives some hints.
If you are still not satisfied, take a look at ASP.NET
Leveraging the "Active Record" approach you may start with something like this:
public final class User {
public init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
public var firstName: String
public var lastName: String
public internal(set) var id: Int
}
protocol ActiveRecord {
static func create(object: Self) throws -> Self
static func fetch(id: Int) throws -> Self
static func update(object: Self) throws -> Self
static func delete(id: Int) throws
}
extension User: ActiveRecord {
static func create(object: User) throws -> User {
...
}
static func fetch(id: Int) throws -> User {
...
}
static func update(object: User) throws -> User {
...
}
static func delete(id: Int) throws {
...
}
}

"foreign key" as primary key in realm

I'm getting started with realm and trying to figure out the best way to write my model layer, so after overthinking about it I decide to ask here to get some opinions. Here we go:
I'm writing a realm version of these tables:
Account
id - String (PK)
Name - String
Type - Int
Checking Account
id - String (FK PK)
In this case a Checking Account is an Account, but realm still not support inheritance (I think using Account as a superclass would be the better way to figure these tables out in the model layer)
This is what I actually did with realm:
Account class:
import Foundation
import RealmSwift
class Account: Object {
dynamic var id = NSUUID().UUIDString
dynamic var name = ""
dynamic var type = 3
override static func primaryKey() -> String {
return "id"
}
}
Checking Account class:
import Foundation
import RealmSwift
class CheckingAccount: Object {
dynamic lazy var id: String = {
return self.account.id
}()
dynamic var account = Account()
convenience init(name: String, type: AccountType) {
self.init()
self.account.name = name
self.account.type = type.rawValue
}
override static func primaryKey() -> String? {
return "id"
}
}
What about it? It's a "Don't do" way to figure out my SQL tables with realm?
Thank you!

Resources