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.
Related
I've found that it's a little easier to explain what I'm doing by giving too much context for why I'm trying to do it, sorry.
I'm currently trying to add an encryption service to my project. It's nothing that'll get published I think though and I've mostly got it working. The problem that I'm having is that I have my model like this
struct Entity {
// All types are examples though I think they will be all optionals.
var prop1: String?
var prop2: Int?
var prop3: Bool?
var encryptedData: [Keypath:EncryptedData]
static var encryptableKeyPaths: [WritableKeyPath<Entity, Any?>]
}
As an example for what's happening, I can get the encryptionService to take in prop1, create an EncryptedData and put it in the encryptedData dictionary. I can even get the keyPath for the property. I can encrypt all the data and decrypt it just fine and get all the values properly, so I don't need help with that. But I'm struggling with 3 issues.
Getting the KeyPaths to be WritableKeyPaths so I can write to them with the values I need.
Setting the properties to nil once the values are encrypted so I'm not storing extra data.
Setting the properties to their values once their decrypted.
All three of these issues seem to revolve around making the KeyPaths into WritableKeyPaths.
This is the closest attempt I've gotten so far. You can copy the following code right into a playground and run it and it should work. Except it'll crash at the end. There are a couple of issues here, I'm losing the type safety as I have to make all the property types Initializable? which isn't great. Also, see that the values are permanently wrapped. I can't figure out how to prevent that. I had to mark Optional as conforming to Initializable to make this work. Lastly, the variable allStoredProperties doesn't let me write to them. I'm not sure how to properly convert it to WritableKeyPath from PartialKeyPath.
import UIKit
protocol Initializable {}
extension String: Initializable {}
extension Int: Initializable {}
extension Bool: Initializable {}
extension Optional: Initializable {}
protocol KeyPathIterable {
associatedtype Model
init()
static var allKeyPaths: [WritableKeyPath<Model, Initializable?>] { get }
}
extension KeyPathIterable {
var keyPathReadableFormat: [String: Initializable] {
var description: [String: Initializable] = [:]
let mirror = Mirror(reflecting: self)
for case let (label?, value) in mirror.children {
description[label] = (value as! Initializable)
}
return description
}
static var allStoredProperties: [PartialKeyPath<Self>] {
var members: [PartialKeyPath<Self>] = []
let instance = Self()
for (key, _) in instance.keyPathReadableFormat {
members.append(\Self.keyPathReadableFormat[key])
}
return members
}
static func setValue<Self: KeyPathIterable, T: Initializable>(on root: inout Self,
at keyPath: WritableKeyPath<Self, Initializable?>,
withValue value: T?) throws {
root[keyPath: keyPath] = value
}
}
struct Foo: KeyPathIterable {
typealias Model = Foo
var prop1: Initializable? // I want this to be String?
var prop2: Initializable? // I want this to be Int?
var prop3: Initializable? // I want this to be Bool?
init() {
self.prop1 = nil
self.prop2 = nil
self.prop3 = nil
}
static var allKeyPaths: [WritableKeyPath<Foo, Initializable?>] {
return [\Model.prop1, \Model.prop2, \Model.prop3]
}
}
var foo = Foo()
foo.prop1 = "Bar"
foo.prop2 = 1
foo.prop3 = true
print(foo.prop1 as Any)
let keyPath = \Foo.prop1
foo[keyPath: keyPath] = "Baz"
print(foo.prop1 as Any)
for path in Foo.allStoredProperties {
print("-=-=-")
print(path)
}
print("-=-=-=-=-=-=-=-")
do {
try Foo.setValue(on: &foo, at: keyPath, withValue: "BazBar" as Initializable?)
} catch {
print("Should never fail")
}
print(foo.prop1 as Any) // Returns Optional(Optional("BarBaz")) - I want this to be all the way unwrapped.
print("--------------")
let values1: [Initializable] = ["Hello World", 100, false]
do {
for (path, value) in zip(Foo.allKeyPaths, values1) {
try Foo.setValue(on: &foo,
at: path,
withValue: value as Initializable?)
}
} catch {
print("Success?")
}
print(foo.prop1 as Any)
print(foo.prop2 as Any)
print(foo.prop3 as Any)
print("----====----====----")
let values2: [Initializable] = ["Howdy", 0, false]
do {
for (path, value) in zip(Foo.allStoredProperties, values2) {
try Foo.setValue(on: &foo,
at: path as! WritableKeyPath<Foo, Initializable?>,
withValue: value as Initializable?)
}
} catch {
print("Always fails")
}
print("=-=-=-=-=-=-=-=")
print(foo)
I've looked all over google and youtube and everywhere and I can't seem to get this to work. I'm open to a different architecture if that work accomplish my goals better. Just a little frustrated. Thanks for your help.
Let's say I have that struct:
struct MyStruct {
let x: Bool
let y: Bool
}
In Swift 4 we can now access it's properties with the myStruct[keyPath: \MyStruct.x] interface.
What I need is a way to access all it's key paths, something like:
extension MyStruct {
static func getAllKeyPaths() -> [WritableKeyPath<MyStruct, Bool>] {
return [
\MyStruct.x,
\MyStruct.y
]
}
}
But, obviously, without me having to manually declare every property in an array.
How can I achieve that?
DISCLAIMER:
Please note that the following code is for educational purpose only and it should not be used in a real application, and might contains a lot of bugs/strange behaviors if KeyPath are used this way.
Answer:
I don't know if your question is still relevant today, but the challenge was fun :)
This is actually possible using the mirroring API.
The KeyPath API currently doesn't allow us to initialize a new KeyPath from a string, but it does support dictionary "parsing".
The idea here is to build a dictionary that will describe the struct using the mirroring API, then iterate over the key to build the KeyPath array.
Swift 4.2 playground:
protocol KeyPathListable {
// require empty init as the implementation use the mirroring API, which require
// to be used on an instance. So we need to be able to create a new instance of the
// type.
init()
var _keyPathReadableFormat: [String: Any] { get }
static var allKeyPaths: [KeyPath<Foo, Any?>] { get }
}
extension KeyPathListable {
var _keyPathReadableFormat: [String: Any] {
let mirror = Mirror(reflecting: self)
var description: [String: Any] = [:]
for case let (label?, value) in mirror.children {
description[label] = value
}
return description
}
static var allKeyPaths: [KeyPath<Self, Any?>] {
var keyPaths: [KeyPath<Self, Any?>] = []
let instance = Self()
for (key, _) in instance._keyPathReadableFormat {
keyPaths.append(\Self._keyPathReadableFormat[key])
}
return keyPaths
}
}
struct Foo: KeyPathListable {
var x: Int
var y: Int
}
extension Foo {
// Custom init inside an extension to keep auto generated `init(x:, y:)`
init() {
x = 0
y = 0
}
}
let xKey = Foo.allKeyPaths[0]
let yKey = Foo.allKeyPaths[1]
var foo = Foo(x: 10, y: 20)
let x = foo[keyPath: xKey]!
let y = foo[keyPath: yKey]!
print(x)
print(y)
Note that the printed output is not always in the same order (probably because of the mirroring API, but not so sure about that).
After modifying rraphael's answer I asked about this on the Swift forums.
It is possible, discussion here:
Getting KeyPaths to members automatically using Mirror
Also, the Swift for TensorFlow team has this already built in to Swift for TensorFlow, which may make its way to pure swift:
Dynamic property iteration using key paths
I propose my solution. It has the advantage of dealing correctly with #Published values when using the Combine framework.
For the sake of clarity, it is a simplified version of what I have really. In the full version, I pass some options to the Mirror.allKeyPaths() function to change behaviour ( To enumerate structs and/or classes properties in sub-dictionaries for example ).
The first Mirror extension propose some functions to simplify properties enumeration.
The second extension implements the keyPaths dictionaries creation, replacing
#Published properties by correct name and value
The last part is the KeyPathIterable protocol, that add enumeration
capability to associated object
swift
// MARK: - Convenience extensions
extension String {
/// Returns string without first character
var byRemovingFirstCharacter: String {
guard count > 1 else { return "" }
return String(suffix(count-1))
}
}
// MARK: - Mirror convenience extension
extension Mirror {
/// Iterates through all children
static func forEachProperty(of object: Any, doClosure: (String, Any)->Void) {
for (property, value) in Mirror(reflecting: object).children where property != nil {
doClosure(property!, value)
}
}
/// Executes closure if property named 'property' is found
///
/// Returns true if property was found
#discardableResult static func withProperty(_ property: String, of object: Any, doClosure: (String, Any)->Void) -> Bool {
for (property, value) in Mirror(reflecting: object).children where property == property {
doClosure(property!, value)
return true
}
return false
}
/// Utility function to determine if a value is marked #Published
static func isValuePublished(_ value: Any) -> Bool {
let valueTypeAsString = String(describing: type(of: value))
let prefix = valueTypeAsString.prefix { $0 != "<" }
return prefix == "Published"
}
}
// MARK: - Mirror extension to return any object properties as [Property, Value] dictionary
extension Mirror {
/// Returns objects properties as a dictionary [property: value]
static func allKeyPaths(for object: Any) -> [String: Any] {
var out = [String: Any]()
Mirror.forEachProperty(of: object) { property, value in
// If value is of type Published<Some>, we transform to 'regular' property label and value
if Self.isValuePublished(value) {
Mirror.withProperty("value", of: value) { _, subValue in
out[property.byRemovingFirstCharacter] = subValue
}
} else {
out[property] = value
}
}
return out
}
}
// MARK: - KeyPathIterable protocol
protocol KeyPathIterable {
}
extension KeyPathIterable {
/// Returns all object properties
var allKeyPaths: [String: Any] {
return Mirror.allKeyPaths(for: self)
}
}
I have been struggling with solving a problem which I am pretty sure leads me to adopt the Type-Erasure technique but I am not 100% sure. I have tried several different times and have felt close but ultimately have failed. I will try to simplify my problem. Lets say you have an entity
struct Expense {
var id: Int?
var amount: Double = 0
}
and an equivalent Realm Object
class RealmExpense: Object {
let id = RealmOptional<Int>()
let amount = RealmOptional<Double>()
var entity: Expense {
return Expense(id: id.value, amount: amount.value)
}
}
Note that I can convert from RealmExpense to Expense by using the entity variable.
Then, I have another protocol
protocol ExpenseRepository: class {
func getAll() -> [Expense]
//...other CRUD methods
}
and finally, a class
class ExpenseRealmRepository: ExpenseRepository {
private let realm = try! Realm()
func getAll() -> [Expense] {
return realm.objects(RealmExpense.self).flatMap { $0.entity }
}
func insert(item: Expense) {
try! realm.write {
realm.add(RealmExpense(expense: item))
}
}
//...implementation of other CRUD methods
}
Now, this works fine, but I have many entities, and this is repetitive code that I would like to refactor but every attempt I have made to make this generic throws one compiler error or another. What I would like is to basically be able to create a class
class RealmRepository<RepositoryType: Object, EntityType> {
private let realm = try! Realm()
func getAll() -> [EntityType] {
return realm.objects(RepositoryType.self).flatMap { $0.entity }
}
func insert(item: EntityType) {
try! realm.write {
realm.add(RepositoryType(item))
}
}
//...the rest of the CRUD methods.
}
My main problem is that it seems that Swift generics do not allow this type of behavior. Maybe there is a better overall approach that I have not explored.
Note: Small addendum to Dave Weston's answer below:
The answer given by David Weston below is correct (and excellent) but there is currently a Swift bug that prevents the Realm Entity initializer from being used. See:
https://github.com/realm/realm-cocoa/pull/2514
Basically, the solution is to use the default initializer for Realm Objects which accept a dictionary. Since I am already using the ObjectMapper library to convert entities to and from JSON this was as simple as changing
func insert(item: T.EntityType) {
realm.write {
realm.add(RealmEntityType(item)) //compiler error when initializing RealmEntityType
}
}
to
func insert(item: T.EntityType) {
let realmItem = RealmEntityType()
realmItem.setValuesForKeys(item.toJSON()) //needs to be a dictionary
try! realm.write {
realm.add(realmItem)
}
}
This was an interesting problem. I'm not sure how you'll be using these repository objects, but based on your question so far, I didn't need to use type erasure.
First, we define the generic hierarchy that we need:
// implemented by your immutable structs
protocol Entity {
}
// Implemented by RealmSwift.Object subclasses
protocol RealmEntity {
associatedtype EntityType
init(_ entity: EntityType)
var entity: EntityType { get }
}
// protocol to define the operations one can perform on a repository
protocol Repository {
associatedtype EntityType
func getAll() -> [EntityType]
func insert(item: EntityType)
}
// A RealmRepository that implements the Repository protocol on top of Realm
class RealmRepository<T>: Repository where T: RealmEntity, T: Object, T.EntityType: Entity {
typealias RealmEntityType = T
private let realm = Realm()
internal func insert(item: T.EntityType) {
realm.write {
realm.add(RealmEntityType(item))
}
}
internal func getAll() -> [T.EntityType] {
return realm.objects(T.self).flatMap { $0.entity }
}
}
Now that we have the infrastructure, we can create an ExpenseRepository to show that all of this stuff compiles:
struct Expense: Entity {
var id: Int
var amount: Double
}
class RealmExpense: Object, RealmEntity {
typealias EntityType = Expense
let id: RealmOptional<Int>
let amount: RealmOptional<Double>
required init(_ entity: EntityType) {
id = RealmOptional(entity.id)
amount = RealmOptional(entity.amount)
}
var entity: Expense {
return Expense(id: id.value!, amount: amount.value!)
}
}
var expenseRepository = RealmRepository<RealmExpense>()
This may not compile in your project, since I created in a playground that didn't import Realm, but it should give you an idea.
Now, if you want to store an array of repositories of different types, or a variable of type Repository, that's when you'll need type erasure, and have to create an AnyRepository.
for all those who are looking into such kind of solution, I have made a sample taking help from the discussions above. I am posting the link for the repo below
https://github.com/Rj707/GenericRealm
I am trying to write a library, you can find it on:
https://github.com/rusito-23/RealmDAO
UPDATE:
The basic idea is to handle all your Realm transactions inside a class called GenericDAO, that has two associated types, one that extends from Object, and another that extends from Transferrable (this way we can handle a copy of the object and not the object itself).
There is also an extra protocol and his method to handle Autoincremental primary keys!
It should be as simple to use as:
let movieDAO = GenericDAO<Movie>()
movieDAO.findAll(completion: { /*-- YOUR CODE --*/})
You can find more information on the link I sent!
Hope it helps!
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
}
I'm currently using Realm Swift 1.0.1.
Say you have a Realm Object, that has another Realm Object as a property, like so:
import RealmSwift
class Car: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var model = ""
}
class Garage: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var carStored: Car?
}
If you then create new Car and Garage objects, with the Car being a property of the Garage... but only write the new Garage to the Realm, like so...
let teslaCar = Car()
teslaCar.id = 1
teslaCar.model = "Tesla"
let myGarage = Garage()
myGarage.id = 1
myGarage.carStored = teslaCar
let realm = try! Realm()
try! realm.write {
realm.add(myGarage, update: true)
}
... will the write cascade, and also save the teslaCar to the Realm as well as myGarage?
The Realm Swift write docs: https://realm.io/docs/swift/latest/#writes
I just tested it in one of the Realm sample apps to be absolutely sure. Yes, if you set an object as a linked object of another Realm object, both will be added to Realm in the next write transaction.