Check if Realm Object contains a specific value in array - Swift - swift

When you have a Realm model that looks something like:
class Thing {
var id: String?
var kids: List<String>
}
And have Thing objects like: Thing(id: "thingyOne", kids: List<"Momo", "Jojo">). You can easily query for objects that have id of "thingyOne" using Realm().objects(Thing.self).filter("id == 'thingyOne'"). How do you check which objects have kids named "Jojo" in their kids list?
I'm currently using this method:
let things = Realm().objects(Thing.self)
for thing in things {
if thing.kids.contains("Jojo") {
// Success
}
}
This makes the app extremely slow because I have thousands of Realm objects. How do you do it the correct way?

Related

Implementing Codable and NSManagedObject simultaneously in Swift

I have an order processing application I'm working on for my employers that was originally designed to get all data about orders, products, and clients dynamically from the API. So all of the objects and and all of the functions dealing with those objects are interacting in the app with a "pass by value" expectation, utilizing structs conforming to Codable.
I now have to cache pretty much all of these objects. Enter CoreData.
I have no desire to create two files for one object(one as a Codable struct and the other as an NSManagedObject class) and then trying to figure out how to convert one to another. So I want to implement both in the same file...while still somehow being able to use my "pass by value" code.
Perhaps this is impossible.
edit
I'm looking for something a bit simpler than rebuilding all my data structures from the ground up. I understand I'll have to do some alterations to make a Codable struct compatible with a NSManagedObject class. I'd like to avoid making a custom initializer that requires me to enter in every property by hand, because there's hundreds of them.
In the end, it sounds like there is no "good" solution when migrating from an API dynamic app without caching to a cached app.
I decided to just bite the bullet and try the method in this Question: How to use swift 4 Codable in Core Data?
EDIT:
I couldn't figure out how to make that work so I used the following solution:
import Foundation
import CoreData
/*
SomeItemData vs SomeItem:
The object with 'Data' appended to the name will always be the codable struct. The other will be the NSManagedObject class.
*/
struct OrderData: Codable, CodingKeyed, PropertyLoopable
{
typealias CodingKeys = CodableKeys.OrderData
let writer: String,
userID: String,
orderType: String,
shipping: ShippingAddressData
var items: [OrderedProductData]
let totals: PaymentTotalData,
discount: Float
init(json:[String:Any])
{
writer = json[CodingKeys.writer.rawValue] as! String
userID = json[CodingKeys.userID.rawValue] as! String
orderType = json[CodingKeys.orderType.rawValue] as! String
shipping = json[CodingKeys.shipping.rawValue] as! ShippingAddressData
items = json[CodingKeys.items.rawValue] as! [OrderedProductData]
totals = json[CodingKeys.totals.rawValue] as! PaymentTotalData
discount = json[CodingKeys.discount.rawValue] as! Float
}
}
extension Order: PropertyLoopable //this is the NSManagedObject. PropertyLoopable has a default implementation that uses Mirror to convert all the properties into a dictionary I can iterate through, which I can then pass directly to the JSON constructor above
{
convenience init(from codableObject: OrderData)
{
self.init(context: PersistenceManager.shared.context)
writer = codableObject.writer
userID = codableObject.userID
orderType = codableObject.orderType
shipping = ShippingAddress(from: codableObject.shipping)
items = []
for item in codableObject.items
{
self.addToItems(OrderedProduct(from: item))
}
totals = PaymentTotal(from: codableObject.totals)
discount = codableObject.discount
}
}

NSPredicate Filtering With Realm Swift

I'm having some trouble figuring out how to turn ordinary swift filtering code into an NSPredicate query with Realm. I have included a simplified example of my data structure. My goal is to figure out which of the User's contacts from the device are already registered for my app.
class User: Object {
let contacts = List<Contact>()
}
class Contact: Object {
let numbers = List<String>()
}
Each User object contains a list of 'Contacts' which are all the contacts currently stored on the Users device. And each 'Contact' Object has a list of 'numbers' because each contact can have multiple phone numbers.
In regular swift code, figuring out which of the users contacts are already registered for my app looks like this:
func alreadyRegistertedUserContacts(_ contacts: Results<Contact>,
allUsers: Results<User>) -> [Contact] {
return contacts.filter { (contact) -> Bool in
return contact.numbers.contains(where: { (number) -> Bool in
return allUsers.contains(where: { (user) -> Bool in
return user.phoneNumber == number
})
})
}
}
So the question is, for efficiencies sake, how would I change this function to use NSPredicates instead?
Thanks in advance for your help!
Realm does not currently support using properties whose types are lists of primitives in its predicates. Until that limitation is lifted, it's not possible to filter your objects using an NSPredicate alone.
If you were willing to change your model like so:
class StringObject: Object {
#objc dynamic var string = ""
}
class Contact: Object {
let numbers = List<StringObject>()
}
class User: Object {
let contacts = List<Contact>()
}
This should allow you to perform your filtering like so:
return contacts.filter("ANY numbers.value IN %#",
allUsers.flatMap { return $0.phoneNumber })

How to deal with relationships with structs in Swift?

I was advised to use structs as much as possible in Swift, because so many things are structs already (Int, String, Array, Dictionary, almost everything is).
But if I have a relationship (with a reverse) to deal with, I couldn't come up with a satisfying solution, especially in the case of datasource for an UITableView. An example out of many: a Game object with multiple Player, and each Player has multiple Game.
approach one
I store all the instances of the same struct in a list, I give an id to each instance, and I fetch the lists every time I need related objects.
var allGames: [Game] = []
struct Game {
let id: String
var playerIds: [String]
var players: [Player] {
get { return allPlayers.filter({ playerIds.contains($0.id) }) }
}
}
var allPlayers: [Player] = []
struct Player {
let id: String
var gameIds: [String]
var games: [Game] {
get { return allGames.filter({ gameIds.contains($0.id) }) }
}
}
approach two
I move away from struct.
class Game {
var players: [Player] = []
}
class Player {
var games: [Game] = []
}
question
How to deal with relationships with structs in Swift? Is one of the two approaches above a better way than the other, or is there an even better way?
You were told to use structs instead of classes? You've been had. Or you just didn't understand whatever advice you got properly.
structs are value types. Classes are reference types. That means you can't have references to structs. Two structs = two different objeccts = twice the memory. You can have references to class instances.
Say you have 1000 players all playing the same game. With structs, you have 1000 copies of the game. Worse, the game would have a copy of each player, which each would have a copy of the game, which would have a copy of each player, which each would have a copy of the game, and so on forever.
That's why you make Player and Game classes.

Wait for one property to initialize and use it as reference for pulling data from db into another property, best practice?

I have a class, it has two properties:
var fruitsPackId: Int
var fruitsPackContent: Array<Fruit>?
Once the class is being initialized, I want to append data into fruintsPackContent from a local db according the the initialized fruitsPackId. I am not sure what is the best practice on that type of case.
What I did for now, is creating fruitsPackContent as a computed property, that pulls out the data from the local db using the fruitsPackId as reference id.
However, I feel that this is just not the right way of doing it, any ideas?
My code:
class FruitsPack: NSObject {
var fruitsPackId: Int
init(fruitsPackId: Int) {
self.fruitsPackId = fruitsPackId
}
var fruitsPackContent: Array<Fruit>? {
// Pulling data from local db here...
// For this example I create a dummy array with one instance of Fruit
let fruit1 = Fruit(fruitsPackId: self.fruitsPackId, fruitName: "Banana")
var fruits = Array<Fruit>()
fruits.append(fruit1)
return fruits
}
}
class Fruit: FruitsPack {
var fruitName: String
init(fruitsPackId: Int, fruitName: String) {
self.fruitName = fruitName
super.init(fruitsPackId: fruitsPackId)
}
}
EDIT:
Using lazy variable type did the work for me:
Class initialization has nothing to do with that property
Memory is being utilized only once property is being called
The property is being filled up with data only once
An instance method is available to be used by others
New code:
class FruitsPack: NSObject {
var fruitsPackId: Int
lazy var fruitsPackContent: Array<Fruit>? = self.getFruitsPackContent(self.fruitsPackId)
init(fruitsPackId: Int) {
self.fruitsPackId = fruitsPackId
}
func getFruitsPackContent(fruitsPackId: Int) -> Array<Fruit>? {
// Pulling data from local db here...
// For this example I create a dummy array with one instance of Fruit
let fruit1 = Fruit(fruitsPackId: self.fruitsPackId, fruitName: "Banana")
var fruits = Array<Fruit>()
fruits.append(fruit1)
return fruits
}
}
class Fruit: FruitsPack {
var fruitName: String
init(fruitsPackId: Int, fruitName: String) {
self.fruitName = fruitName
super.init(fruitsPackId: fruitsPackId)
}
}
Retrieving data from a database is a relatively computationally expensive process, so I'm not personally a fan of building that into a computed property. What if you innocently had some code that did the following:
for index in 0 ..< fruitPack.fruitsPackContent.count {
print(fruitPack.fruitsPackContent[index])
}
If you had n items in the database, this code might be repeatedly retrieving the full list of all items from the database n + 1 times (once for count and again for each subscripted access). You could, theoretically, remedy this by making sure that the computed property cached the results (and you'd have to build some code that would identify when the database was updated and invalidate the cache).
It would be more prudent to make the retrieval from the database an explicit method so that the app developer knows when they're retrieving data from the database, and when they're accessing a cached array. You generally want to avoid some significant hidden performance impact resulting from innocently accessing some property.

Swift - retrieve at Index within a Directory

In Swift is it possible to retrieve / remove an element in a Dictionary using it's index, not a key value? In this example I'm attempting to create a "to do" list where I can eventually access all the tasks assigned to an individual. But I would also like to remove an element (task) by index.
In the code below how can I remove the second task "wash dishes" by index not using a key value. I was hoping to call this func with something like: taskMgr.removeTaskByIndex(1)
Any additional explanation or advice is helpful as I'm just learning Swift.
import Foundation
class TaskManager{
var taskDictArray = Dictionary<String, String>()
init(){
taskDictArray = [:]
}
func addTask(task: String, person: String){
taskDictArray[task] = person
}
}
var taskMgr = TaskManager()
taskMgr.addTask("take out trash", person: "emma")
taskMgr.addTask("wash dishes", person: "jack")
taskMgr.addTask("clean floor", person: "cleo")
//taskMgr.removeTaskByIndex(1)
//var a = taskMgr.getTaskByIndex(1)
Dictionaries do not maintain their object order and the only way to get a value is using its key. You'll probably want to associate whatever list of tasks you have the index for with its corresponding key and use that manipulate the dictionary. This is the only way. You probably consider restructuring your application logic if it's impossible.
You'll need to add a function such as
func removeTask(task: String) {
taskDictArray[task] = nil
}
So find a way to get the actual task string with which you are associating it.