I want to make an application which requires a lot of weather forecast data, three cities, seven days, 24 hours and six values in every hour of forecast.
I'm using the Dark Sky API and pod called ForecastIO.
What should I use for storage? CoreData or Realm? I was told that Realm is a lot of easier to work with and it's also more efficient. I looked into code and for a beginner it is much easier and not dealing with any graphs is also plus, but if it needs to be done, I'll study it. And how should I structure it?
I tried this but Realm accepts only basic data types, so it won't work.
HourlyWeather.swift
import Foundation
import RealmSwift
class HourlyWeather: Object {
#objc dynamic var temperature: Double = 0
#objc dynamic var wind: Double = 0
#objc dynamic var precip: Double = 0
#objc dynamic var humidity: Double = 0
#objc dynamic var uvIndex: Int = 0
#objc dynamic var icon: String = ""
}
DailyWeather.swift
import Foundation
import RealmSwift
class DailyWeather: Object {
#objc dynamic var day = [HourlyWeather()] // I understand that this is a no-no for Realm
}
CityWeather.swift
import Foundation
import RealmSwift
class CityWeather: Object {
#objc dynamic var city = [DailyWeather()] // The same
}
I wanted this code to be accessible as
City.day[index].hour[index].temperature
for example
You need to use List instead of Array when you want to store a collection of Realm objects as a property of another object. For more information, read the Many-to-many relationships part of the official docs (but I'd suggest going through the whole documentation, since it gives a really good starting point for using Realm).
class DailyWeather: Object {
let day = List<HourlyWeather>()
}
class CityWeather: Object {
let city = List<DailyWeather>()
}
You need to declare a property of type List. See the Many to Many docs
class DailyWeather: Object {
let city = List<DailyWeather>()
}
Related
So I've got a collection of objects - Category
Each Category object has a field called total which contains another object, a Total
Each Total object has a field called amount which is a simple double
I want to observe any changes to the amount field of all the Total objects in my collection of Category... Basically, if I could add an observer onto each Category object's total. Does that makes sense? It's much harder to describe that I thought. haha
I've tried all kinds of different way but all either end up in memory leaks or horribly inefficient and breaking the typical Realm way of handling data.
Something to note - there are thousands of TartuGecko objects... Anything in the app that relates to an amount is stored as a TartuGecko. Therefore, observing them all is impractical.
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
dynamic var total: TartuGecko?
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
}
There are some inconsistencies in how you are describing things (as noted by Jay) but assuming you just need the concepts, this is how you could structure your Realm models:
final class CategoryTotal: QueryableModelObject {
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
}
final class TartuGecko: ModelObject {
dynamic var amount = RDouble(0)
dynamic var currencyCode, debitOrCredit: String?
dynamic var exchangeRate = RDouble(0)
dynamic var categoryTotal: CategoryTotal?
}
Then you can access all the TartuGecko objects on each CategoryTotal object with categoryTotal.tartuGeckos like this:
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.tartuGeckos) //<-- All your TartuGeckos that point to that category are available here
}
You will have to make sure that each TartuGecko object that gets created has a reference to a CategoryTotal.
If you just want a total amount for each CategoryTotal, then a more direct solution would be to use a computed property like this:
final class CategoryTotal: QueryModelObject{
dynamic var categoryId: String?
let tartuGeckos = LinkingObjects(fromType: TartuGecko.self, property: "categoryTotal")
//Computed property
var totalAmount: Double{
var total = 0.0
//There are fancier ways of calculating this, but I'm trying to be clear
for tartuGecko in tartuGecko{
total += tartuGecko.amount
}
return total
}
}
Then on each CategoryTotal object you always have the total amount available to you.
let realm = try! Realm()
let categories = realm.objects(CategoryTotal.self)
for category in categories{
print(category.totalAmount) //<-- Total amount
}
I'm brand new to this, so forgive me if I'm missing something obvious or not asking the right question. I plan to make an app that have a few sets of data that require many-to-many relationships. For example, if I have a model for Food items and a model for CookingMethods to cook that food item. So, each Food can have multiple CookingMethods, and each CookingMethod applies to multiple types of Food.
I think this is the right way to set up the realm data:
class Food: Object {
#objc dynamic var title: String = ""
#objc dynamic var id: Int = 0
var cookingMethods = List<CookingMethod>()
}
class CookingMethod: Object {
#objc dynamic var id: Int = 0
#objc dynamic var title: String = ""
let foods = LinkingObjects(fromType: Food.self, property: "cookingMethods")
}
Is it now possible to import a set of data (probably a csv file?) using either Realm Studio or programmatically that allows me to link this relationship? The Food would have a list of CookingMethods, and the CookingMethods would link back to multiple different Foods?
If I'm going about this all wrong please let me know, there is a spreadsheet of data that I'd like to add to my app's database as a one time thing.
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
}
}
I am trying to implement below scenario, but i am facing the issue
class CommentsModel: Object {
dynamic var commentId = ""
dynamic var ownerId: UserModel?
dynamic var treeLevel = 0
dynamic var message = ""
dynamic var modifiedTs = NSDate()
dynamic var createdTs = NSDate()
//facing issue here
dynamic var childComments = List<CommentsModel>()
}
I have a comments model which has non optional properties in which childComments is List of same Comments model class. In this when i declare dynamic var childComments = List<CommentsModel>()
it shows me Property cannot be marked dynamic because its type cannot
be represented in Objective-C.
Please help me how to achieve my requirement
List and RealmOptional properties cannot be declared as dynamic because generic properties cannot be represented in the Objective‑C runtime, which is used for dynamic dispatch of dynamic properties, and should always be declared with let.
Learn more in Docs.
So you should declare childComments this way:
let childComments = List<CommentsModel>()
Just to make it more understandable how you can add data to the list although its declared as let.
import Foundation
import RealmSwift
import Realm
class Goal: Object {
//List that holds all the Events of this goal
let listOfEvents = List<CalEvent>()
required public convenience init(eventList: List<CalEvent>) {
self.init()
for event in eventList {
//Append the date here
self.listOfEvents.append(i)
}
}
}
I'm using Realm, the project is on version 1.0.0.
When I create a list of Realm Objects (with data obtained from a web API), then try to save them to the Realm using this utility function in a struct:
static func saveRealmObjects(objects: [Object]) {
defer {
// Never entered
}
for object in objects {
let realm = try! Realm()
do {
try realm.write {
print("TEST: 1: object: \(object)")
realm.add(object)
print("TEST: 2")
}
} catch {
// Never entered
}
}
}
(Please don't judge me on the exact structure, I've been toying around seeing if anything will work).
I can tell from liberal use of print statements (mostly removed above) that the function gets to TEST: 1 okay, but fails to make it to TEST: 2, for the very first Object in the list I pass to the function.
I should note this function does work the first time I use it with the data (say after wiping the simulator and launching the app afresh), but then if I recreate the Objects and try to save them again it gets stuck.
I assumed Realm would use the private key on the Objects and overwrite any if necessary. But it seems to just get stuck.
-
Then - after it's stuck - if I try and get another set of results from Realm (using a different Realm object) I get the following error:
libc++abi.dylib: terminating with uncaught exception of type realm::InvalidTransactionException: Cannot create asynchronous query while in a write transaction
FYI I'm creating a different Realm object using try! Realm()
-
For reference, here is the Object I'm trying to save:
import Foundation
import RealmSwift
class MyObject: Object {
// MARK: Realm Primary Key
dynamic var id: String = ""
override static func primaryKey() -> String? {
return "id"
}
// MARK: Stored Properties
dynamic var date: NSDate? = nil
dynamic var numA = 0
dynamic var numB = 0
dynamic var numC = 0
dynamic var numD = 0
dynamic var numE = 0
dynamic var numF = 0
dynamic var numG = 0
dynamic var numH = 0
// MARK: Computed Properties
var computedNumI: Int {
return numD + numE
}
var computedNumJ: Int {
return numF + numG
}
}
(The variable names have been changed.)
-
Hopefully I'm doing something obviously wrong - this is my first time using Realm after all.
If you have any ideas why it's sticking (perhaps it's a threading issue?), or want more info, please answer or comment. Thank you.
Being the clever clogs I am, I've literally just found the answer by reading the documentation:
https://realm.io/docs/swift/latest/#creating-and-updating-objects-with-primary-keys
The add to Realm line needed to look like this:
realm.add(object, update: true)
Where the update flag will update Objects already saved with that primary key.
-
Although it would have been nice if it either gave some sort of obvious warning or crash upon trying to add the same object, or didn't cause other queries and writes to Realm to crash.