Swift Realm - Creating child Realm objects and linking them to their parent - swift

I am currently learning Realm and am converting my experimental app/game which uses arrays to Realm;
It loads pre-seeding data via a local JSON file and ObjectMapper; then creates objects in realm; this part seems to work.
// Parse response
let json = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! Array<Dictionary<String, AnyObject>>
let factories = Mapper<Factory>().mapArray(JSONArray: json)!
do {
let realm = try Realm()
try realm.write {
for factory in factories
{
realm.add(factory, update: true)
}
}
} catch let error as NSError {
print(error.localizedDescription as Any)
}
The issue I'm having is that when it maps; I'd like it to create its child objects at the same time and link them to parent.
Each parent (Factory) has about between 4 children (Engine) linked to it.
// Factory is parent object
class Factory: Object, Mappable {
dynamic var name: String = ""
let engines = List<Engine>()
//Impl. of Mappable protocol
required convenience init?(map: Map) {
self.init()
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
}
}
// Engine is a child to Factory
class Engine: Object {
dynamic var production: Int = 0
// create children and add to the parent factory
static func createEngines(parent:Factory) -> [Engines]
{
var engines:[Engine] = [Engine]()
for _ in stride(from:0, to: 3, by: 1) {
//let engine : Engine = Engine.init(parent: element)
//engines.append(engine)
}
return engines
}
}
If I attempt to put this in my mappable
engines = Engine.createEngines(parent: self)
and make a change in my Factory model;
`var engines = List<Engine>()`
I get this error:
Cannot assign value of type '[Engine]?' to type 'List<Engine>'
The problem here is that simply creating an array of engines (children), appending it to an array doesn't seem to work with Realm and I'm not sure how to do this.
Hence, my question is how do I bulk create children, assign it to a given parent and add it to the current realm write/save?
Many thanks.

I changed my code to do this;
Read all the factories from JSON
Loop through the factories, creating engines
Link the parent object up.
I'm not sure if I did it right but it seems to be working.
I just don't like how I'm having to hardwire the parent; as I thought Realm/ObjectMapper could do that for me. But its not a major issue as there is only about 3 or 4 relationships.
let factories = Mapper<Factory>().mapArray(JSONArray: json)!
do {
let realm = try Realm()
try realm.write {
for f in factories
{
realm.add(f, update: true)
}
let factories = realm.objects(Factory.self)
print (factories.count) // for debug purposes
for f in factories {
for _ in stride(from: 0, to: f.qty, by: 1) {
let engine : Engine = Engine.init()
engine.parent = f
f.engines.append(engine)
}
}
}
} catch let error as NSError {
print(error.localizedDescription as Any)
}
This above code seems to do the work for me; although I do wish I didn't have to manually set the parent (engine.parent = f)
Anyhow, I've accepted #BogdanFarca's answer.

There is a very nice solution by Jerrot here on Githib Gist
The mapping should be defined in your main model object like this:
func mapping(map: Map) {
title <- map["title"]
products <- (map["products"], ArrayTransform<ProductModel>())
}
The real magic is happening in the ArrayTransform class:
func transformFromJSON(value: AnyObject?) -> List<T>? {
var result = List<T>()
if let tempArr = value as! Array<AnyObject>? {
for entry in tempArr {
let mapper = Mapper<T>()
let model : T = mapper.map(entry)!
result.append(model)
}
}
return result
}

Related

Swift - switch between Core ML Model

I'm trying to compare predictions from different MLModels in SwiftUI. To do that I have to switch between them, but can't because every ML variable has its own class, so I get the error:
Cannot assign value of type 'ModelOne' to type 'ModelTwo'
Here's an example code:
import Foundation
import CoreML
import SwiftUI
let modelone = { //declaration model 1
do {
let config = MLModelConfiguration()
return try ModelOne(configuration: config)
} catch {
/*...*/
}
}()
let modeltwo = { //declaration model 2
do {
let config = MLModelConfiguration()
return try ModelTwo(configuration: config)
} catch {
/*...*/
}
}()
var imageused : UIImage! //image to classify
var modelstring = "" //string of model user chosen
var modelchosen = modelone
Button(action: { //button user decide to use model two
modelstring = "Model Two"
}) {/*...*/}
/*...*/
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
if modelstring == "Model Two" { //if the user chosen model two, use ModelTwo
modelchosen = modeltwo // Error: Cannot assign value of type 'ModelOne' to type 'ModelTwo'
} else {
modelchosen = modelone}
let output = try? modelchosen.prediction(image: imagebuffer) //prediction with model chosen
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/
}
}
}
Thank you!
The issue is that the two model classes do not have a common class or common inherited class. There are several ways to implement what you want. I think this is the best way based on your example.
class MyModel {
var model: MLModel? = nil
init(modelName: String) {
let bundle = Bundle.main
if let modelURL = bundle.url(forResource: modelName, withExtension:"mlmodelc") {
do {
self.model = try MLModel(contentsOf: modelURL)
}
catch {
print("Unable to open MLModel: \(error)")
}
}
}
}
class TestModel {
class func testModels() {
let modelOne = MyModel(modelName: "ModelOne")
let modelTwo = MyModel(modelName: "ModelTwo")
var selectedModel = modelOne
selectedModel = modelTwo
}
}
Swift is a statically typed language which means that in the general case you cannot assign a variable of one type to a variable of another type:
var int: Int = 42
int = "Hello, world!" // Not allowed: cannot assign String to Int
The problem is that modelchosen is of type ModelOne since it is initialized with modelone, thus, you cannot later assign modeltwo to it as you are trying to do.
To make that working, you have first to identify the common capabilities of ModelOne and ModelTwo. Take a look at their definition. For instance, do their .predict(image:) method return the same type? It looks like you are trying to do image classification, so a common capability could be the capability to return a String describing the image (or a list of potential objects, etc.).
When you'll have identified the common capability, you'll be able to define the common interface of your different types. This common interface can be expressed in many ways:
Using a base class
Using a protocol
Using an enum with payloads (union)
The following examples suppose that the common capabilities are:
The two networks can both be initialized with a MLModelConfiuration
They are used for image classification, i.e. they predict label (a String) describing a given image
Using a base class
The base class definition expresses those requirements like this:
class MLClassifier {
init(from config: MLModelConfig) {
fatalError("not implemented")
}
func classify(image: ImageBuffer) -> String {
fatalError("not implemented")
}
}
You then derive this base class for the two models (example with the first one:
final class ModelOne: MLClassifier {
init(from config: MLModelConfig) {
// the specific implementation for `ModelOne`...
}
func classify(image: ImageBuffer) -> String {
// the specific implementation for `ModelOne`..
}
}
Finally, you can make the variable modelchosen to be of type MLClassifier to erase the underlying concrete type of the model:
var modelchosen: MLClassifier = ModelOne(from: config1)
As MLClassifier is a common base class for both ModelOne and ModelTwo you can dynamically change the type of modelchosen whenever you need:
// Later...
modelchosen = ModelTwo(from: config2)
The variable modelchosen being of type MLClassifier ensures that you can call the .classify(image:) method whatever the concrete model type is:
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
let output = modelchosen.classify(image: imageBuffer)
// Update the UI...
}
Using protocols
Protocols are the modern and preferred way of expressing common interfaces in Swift, they should be used over classes when possible:
protocol MLClassifier {
init(from config: MLModelConfig)
func classify(image: ImageBuffer) -> String
}
// Implement the protocol for your models
struct ModelOne: MLClassifier {
init(from config: MLModelConfig) { ... }
func classify(image: ImageBuffer) -> String { ... }
}
// Store an instance of any `MLClassfier` using an existential
var classifier: any MLClassifier = ModelOne(from: config1)
// Later...
classifier = ModelTwo(from: config2)
To sum up, the key is to identify the common capabilities of the different types you are trying to unify. For instance, if the two models output at some point a classLabelProbs of the same type, then you could use this as the common abstraction.
As a last resort, you could wrap everything in a big if-else statement, event though it is not recommended since it is not very readable, is not a good way to encapsulate common behavior and leads to a lot of code repetition:
func classifyphoto() {
guard let image = imageused as UIImage?,
let imagebuffer = image.convertToBuffer() else {
return
}
if modelstring == "Model Two" {
// Use modeltwo
let output = try? modeltwo.prediction(image: imagebuffer)
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/ }
} else {
// Use modelone
let output = try? modelone.prediction(image: imagebuffer)
if let output = output {
let results = output.classLabelProbs.sorted { $0.1 > $1.1 }
_ = results.map { /*...*/ }
}
}

Querying nested children in Vapor's Fluent

Env: Vapor/Fluent 4.0.0
I have a tree structured data with model like this:
final class Ingredient: Model {
static let schema = "ingredients"
#ID(key: "id")
var id: UUID?
#Field(key: "name")
var name: String
#OptionalParent(key: "parent_id")
var parent: Ingredient?
#Children(for: \.$parent)
var children: [Ingredient]
}
And I want to return the whole tree as a JSON in one of API methods.
func index(req: Request) throws -> EventLoopFuture<[APIIngredient]> {
// Gathering top-level nodes without parents
let ingredients = try Ingredient.query(on: req.db)
.filter(\.$parent.$id == nil)
.sort(\.$name)
.all()
.wait()
enter code here
// Creating API models
let apiIngredients = try ingredients.map {
try APIIngredient(
ingredient: $0,
childrenGetter: {
try $0.$children.query(on: req.db).all().wait()
}
)
}
return req.eventLoop.future(apiIngredients)
}
But I've found that .wait() is disallowed to use in request handlers. What's the right way to approach this?
wait() is not allowed because it blocks the EventLoop. So you have a few options:
Use eager loading as suggested by Nick. This will be much more efficient as it's only 2 DB queries instead of N+1.
Use async/await to write it how you want
Handle the futures properly. Instead of using wait() switch to handling the futures:
func index(req: Request) throws -> EventLoopFuture<[APIIngredient]> {
// Gathering top-level nodes without parents
return Ingredient.query(on: req.db)
.filter(\.$parent.$id == nil)
.sort(\.$name)
.all()
.flatMap { ingredients in
ingredients.map { ingredient -> EventLoopFuture<APIIngredient>
let future = ingredient.$children.query(on: req.db).all().map { children in
APIIngredient(ingredient: ingredient, children: children)
}
}.flatten(on: req.eventLoop)
}
}

Realm Swift Error: Embedded objects cannot be created directly

I am trying to migrate an object with a property of type List<String> to type List<ChildObject> where ChildObject is a custom EmbeddedObject.
Example
Here's what I mean:
import RealmSwift
final class ParentObject: Object {
// Previously, this property was of type `List<String>`.
#Persisted public var children: List<ChildObject>
}
final class ChildObject: EmbeddedObject {
#Persisted var name = ""
}
I'm using this code to perform the migration, which is producing the error:
Embedded objects cannot be created directly
let configuration = Realm.Configuration(schemaVersion: 1) { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
let childrenObjects = newObject!["children"] as! List<MigrationObject>
// I'm trying to retain the previous values for `children` (of type `String`)
// where each value is used as the `name` property of a new `ChildObject`.
for string in childrenStrings {
childrenObjects.append(
// This line produces the error :(
migration.create(ChildObject.className(), value: [string])
)
}
}
}
}
let realm = try! Realm(configuration: configuration)
Question
How do I perform the migration while retaining the previous values?
The easiest thing to do is to create a Dictionary with all of the property name/value pairs of each child object and create the new List in its entirety with an Array of those pairs.
But first you need to extract the String values from the old List of children. The reason this is necessary is because Realm does not represent the element of List<String> as an actual String (which is a struct). We can extract the actual value from the description field:
childrenStrings
.map { String(describing: $0) }
Once you have the name you can represent the new ChildObject with a Dictionary. Note that you will have to include all property names and values in the Dictionary. Since we only have one, called "name", we include that:
childrenStrings
.map { String(describing: $0) }
.map { ["name": $0] }
Your error message said:
Embedded objects cannot be created directly
However, you can create your new objects using an Array of Dictionary where the Array corresponds to the List and each Dictionary corresponds to a ChildObject object. Putting it all together we have:
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
newObject?.setValue(
Array(
childrenStrings
.map { String(describing: $0) }
.map { ["name": $0] }
),
forKey: "children"
)
}
To summarize the solutions from Rob and Jay respectively:
Option 1
This is Rob's solution with modifications.
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
newObject!.setValue(
Array(childrenStrings.map { [$0] }),
forKey: "children"
)
}
Option 2
This is a solution based on Jay's suggestions. It creates fewer arrays (or dictionaries if using named parameters) and should scale better with more complex objects.
migration.enumerateObjects(ofType: ParentObject.className()) { oldObject, newObject in
let childrenStrings = oldObject!["children"] as! List<DynamicObject>
newObject!["children"] = childrenStrings.map {
let childObject = ChildObject()
childObject.name = "\($0)"
return childObject
} as [ChildObject]
}

Best way to save many different objects to Core Data

I finished my first app this weekend (thanks to you Stack Overflow users!) and I'm currently in the midst of optimizing the code. I already fixed most of the duplicates and un-safe practices but this one is getting me stuck.
Here is how I save all the informations I need for a given cryptocurrency to Core Data when the user adds it to his wallet:
if addedCrypto == "Augur REP" {
if CoreDataHandler.saveObject(name: "Augur", code: "augur", symbol: "REP", placeholder: "REP Amount", amount: "0.00000000", amountValue: "0.0") {
for _ in CoreDataHandler.fetchObject()! {
}
}
}
This is pretty handy for one crypto, but my app supports 25 of them. Currently the above lines are duplicated 24 more times in my code, once for each different crypto.
I thought about using a dictionary where I could save Augur REP as a key and then (name: "Augur", code: "augur", ...") as a value but I couldn't figure out how to properly to do it..
What could be the solution here?
EDIT: Here is the saveObject(...) method:
class func saveObject(name:String, code:String, symbol:String, placeholder:String, amount:String, amountValue:String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CryptosMO", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
managedObject.setValue(code, forKey: "code")
managedObject.setValue(symbol, forKey: "symbol")
managedObject.setValue(placeholder, forKey: "placeholder")
managedObject.setValue(amount, forKey: "amount")
managedObject.setValue(amountValue, forKey: "amountValue")
do {
try context.save()
return true
} catch {
return false
}
}
I think you should create a type alias for a tuple type that stores these information:
typealias CryptosMOInfo = (name:String, code:String, symbol:String, placeholder:String, amount:String, amountValue:String)
And then you can just create a dictionary like this:
let cryptosDictionary: [String, CryptosMOInfo] = [
"Augur REP": (name: "Augur", code: "augur", symbol: "REP", placeholder: "REP Amount", amount: "0.00000000", amountValue: "0.0"),
// ...
]
The signature of the saveObject method can be changed to this:
static func saveOject(cryptosInfo: CryptosMOInfo) -> Bool
Just remember to access cryptosInfo:
managedObject.setValue(cryptosInfo.name, forKey: "name")
managedObject.setValue(cryptosInfo.code, forKey: "code")
managedObject.setValue(cryptosInfo.symbol, forKey: "symbol")
managedObject.setValue(cryptosInfo.placeholder, forKey: "placeholder")
managedObject.setValue(cryptosInfo.amount, forKey: "amount")
managedObject.setValue(cryptosInfo.amountValue, forKey: "amountValue")
If you don't like the type alias, you can change it into a struct as well.
Why not work with the managed object subclass directly?
Create a new instance when a new crypto is added and then save all aded/updated objects at once.
if addedCrypto == "Augur REP" {
let crypto = CryptosMO(context: CoreDataHandler.getContext())
crypto.name = "Augur"
crypto.code = "augur"
// and so on
}
Since this code seems to be the same you could create factory methods in for each crypto you support to make the code easier to read.
if addedCrypto == "Augur REP" {
_ = CryptoFactory.createAugur()
}
class CryptoFacory {
static func CreateAugur() -> CryptoMO {
return create(name: "Augur", code: "augur",...
}
//... other crypto factory methods
private static create(name: String, code: String,...) -> CryptoMO {
let crypto = CryptosMO(context: CoreDataHandler.getContext())
crypto.name = name
crypto.code = code
//...
return crypto
}
Then the save method in CoreDataHandler would not need any arguments since the crypto instance is already in the managed object context. So it would simply be
func save() -> Bool {
let context = getContext()
do {
try context.save()
return true
} catch {
return false
}
}

How to save a struct to realm in swift?

It is easy to use Realm with classes by inheriting from Object. But how would I save a struct containing several fields to realm in Swift? E.g.
struct DataModel {
var id = 0
var test = "test"
}
I know the documentation is clear about supported types. But maybe there is nice workaround or - even better - someone from realm could write about future plans about structs.
I' suggest you to use protocols, to achive what you want.
1) Create your Struct
struct Character {
public let identifier: Int
public let name: String
public let realName: String
}
2) Create your Realm Object
final class CharacterObject: Object {
dynamic var identifier = 0
dynamic var name = ""
dynamic var realName = ""
override static func primaryKey() -> String? {
return "identifier"
}
}
3) Use protocols to transform our struct to Realm Object
public protocol Persistable {
associatedtype ManagedObject: RealmSwift.Object
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
4) Make your struct persistable
extension Character: Persistable {
public init(managedObject: CharacterObject) {
identifier = managedObject.identifier
name = managedObject.name
realName = managedObject.realName
}
public func managedObject() -> CharacterObject {
let character = CharacterObject()
character.identifier = identifier
character.name = name
character.realName = realName
return character
}
}
With these tools in place, we are ready to implement the insertion methods of our persistence layer.
5) Exemple to write datas
public final class WriteTransaction {
private let realm: Realm
internal init(realm: Realm) {
self.realm = realm
}
public func add<T: Persistable>(_ value: T, update: Bool) {
realm.add(value.managedObject(), update: update)
}
}
// Implement the Container
public final class Container {
private let realm: Realm
public convenience init() throws {
try self.init(realm: Realm())
}
internal init(realm: Realm) {
self.realm = realm
}
public func write(_ block: (WriteTransaction) throws -> Void)
throws {
let transaction = WriteTransaction(realm: realm)
try realm.write {
try block(transaction)
}
}
}
5) Use the magic!
let character = Character(
identifier: 1000,
name: "Spiderman",
realName: "Peter Parker"
)
let container = try! Container()
try! container.write { transaction in
transaction.add(character)
}
Amazing source : Using Realm with Value Types & My Article
To save a struct in Realm, means copying the data into a Realm Object. The reason why Realm Objects are classes and not structs is because they are not inert values, but auto-updating objects that represent the persisted data in Realm. This has practical benefits, such as the fact that a Realm Object's data is lazy loaded.
You can take advantage of Realm's approach by responding to the change notifications from a Realm instance. For example if your UITableView data source is based off an array property on a Realm Object, as long as you have an instance of that object, you are guaranteed that after the notification it represents the correct values. Used properly this can simplify your code versus having multiple copies of values as structs.
Swift 4 shortest answer
Save structs as Data in Realm
struct MyStruct : Codable { // Variables here }
class MyRealObject : Object {
#objc private dynamic var structData:Data? = nil
var myStruct : MyStruct? {
get {
if let data = structData {
return try? JSONDecoder().decode(MyStruct.self, from: data)
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
Use the magic
let realm = try! Realm()
try! realm.write {
let myReal = MyRealObject()
myReal.myStruct = MyStruct(....)
realm.add(myReal)
}
You can do what suggests Ludovic, or you can automate that process and get rid of that boilerplate code for each of your structs by using Unrealm.