Swift/Realm only adding last object in provided array (SwiftyJSON) - swift

I have the following function that I've been working with on to try and keep my code DRY. Everything works perfectly except for only the last object in JSON is being added to Realm. The json param is being coming from alamofire and being converted by SwiftyJSON.
If you add print(object) it's showing 4 objects. Does anyone have any idea why Realm would only save the last iteration of my for loop, but none of the others? Thanks for the help, I'm losing my mind on this one.
Thank you!
/**
Takes the json provided by *fetchData* to build and save a Realm object by mapping properties to DB attributes. This was built to follow a DRY approach rather than re-inventing the wheel everytime we have to populate our Realm database.
- Parameters:
- json: JSON pulled from the mobile API by using a call like *fetchData*.
- withObject: A realm object (e.g. Module, Audience, Location) that is used throughout the function to build a saveable object.
- propertyMap: A closure that maps the property value from the json object (in current itteration) to the property of the Realm object.
- **property**: A tuple from SwiftyJSON
- **object**: Used to map property from tuple into realm property.
- Returns: Void
Example
ApplicationManager.parseJSON(JSON(locations), withObject: Location(), propertyMap: [
"title" : { property, object in newObject.setValue(property.1.string!, forKey: "title")},
"id" : { property, object in newObject.setValue(property.1.int!, forKey: "id")}
])
*/
static func parseJSON(json: JSON, withObject: Object, propertyMap: [String: (property: (String, JSON), object: Object) -> ()]) {
// Itterate over every object in the json and provide each object.
let realm = self.buildRealm()
for object in json {
realm.beginWrite()
// Itterate over every object and provide each property.
for property in object.1 {
// Make sure the property in the JSON is actually in our property map.
if let propertyMap = propertyMap[property.0] {
// Execute the property map closure that will set the property.
propertyMap(property: property, object: withObject)
}
}
// Create the object.
realm.add(withObject)
// Commit to realm
try! realm.commitWrite()
realm.refresh()
}
}

If I understand what your after - you want to call parseJSON, and have it create multiple Object instances, and use a closure that will set the properties for each instance. In that case, you should use generics to pass in the Object subclass type, and create a new instance each iteration. Along the lines of:
class func parseJSON<T: Object>(json: JSON, withType: T.Type, propertyMap: [String: (property: (String, JSON), object: Object) -> ()]) {
let realm = try! Realm()
try! realm.write({
for object in json {
let newObject = T()
for property in object.1 {
if let propertyMap = propertyMap[property.0] {
propertyMap(property: property, object: withObject)
}
}
realm.add(newObject)
}
})
}
And then you call it like:
ViewController.parseJSON(JSON, withType: MyClass, propertyMap: myPropertyMap)

Related

Creating observable from another observable

I would like to create Observable object of User. This is my structure
import Foundation
struct User {
let email: String
let username: String
}
When i do network request I get response of type Observable<[String:Any]>, that works fine, but I have no idea how to transform that into Observable<[User]>. I tried
func loadUsers() -> Observable<[User]> {
return fetchUserData(cUser: Master.users).map(([String : Any]) throws -> [User])
}
But I get this error
Cannot convert value of type '(([String : Any]) throws -> [User]).Type' to expected argument type '([String : Any]) throws -> [User]'
I don't know where Observable comes from, there's no class like that in Foundation. Combine has observables, but they're not called that there, they're various types of publishers. Are you using another library like RxSwift?
For the sake of getting you closer to answering the question, let's imagine that fetchUserData returns an array instead.
map needs to take a function that will transform the input from [String:Any] to User. In your case, something like
fetchUserData(...).compactMap { dict in
// I am making "username" and "email" up below,
// you did not mention which keys would exist in the dictionary.
if let username = dict["username"] as? String,
let email = dict["email"] as? String {
return User(email:email, username:username)
} else {
return nil
}
}
I used compactMap, which does the same thing as map, except that when you return an optional (User? in this case), it removes the nil entries.
The reactive framework you're using will have similar calls to do the same thing on Observable, on Publisher, etc. They will also allow you to throw an error instead of returning nil, but they all handle it differently (Combine includes the error type in the type of the Publisher, for example).

Keeping reference to object when creating a closure and storing it

I have a question about how references to objects are handled in closures in specific example. The code below is simplified to demonstrate the idea
class SomeObject {
init(value: Int) {
self.value = value
}
var value: Int {
didSet {
valueObservers.forEach { $0(value) }
}
}
var valueObservers = [(Int) -> Void]()
}
class TestClass {
let obj1 = SomeObject(value: 1)
let obj2 = SomeObject(value: 2)
let obj3 = SomeObject(value: 3)
func valueOfObjectChanged(object: SomeObject) {
print("Value of object \(object) is \(object.value)")
}
init() {
let objects = [obj1, obj2, obj3]
objects.forEach { (object) in
object.valueObservers.append { [unowned self] (_) in
self.valueOfObjectChanged(object: object)
}
}
}
}
So we have here a class SomeObject which can store an array of closures which will be executed when value of its variable changes. TestClass creates a few of these objects, then makes array from this objects, and for every object in this array it adds a closure that will execute a function of TestClass when a value of any of these SomeObjects changes.
OK now inside this function that can be triggered by any of created objects, I want to be sure that I know which object did actually caused it to execute. The reference is passed in function execution, but note that object here is get from a loop and passed to the closure (closure which will execute valueOfObjectChanged and pass object reference as parameter).
What's the problem with that - I'm not sure if the reference to object will actually be kept for correct object, as this closure is executed somewhere completely different then when the closure was created.
I can't put object in this closure capture list, because this causes Segmentation fault: 11.
I'm not sure how the closure would behave in this situation - will reference to object be kept, or can it be wrong?
I could also add the caller in definition of valueObservers, but this is something I would like to avoid because of current structure of the app (the example is just a simplification).
So my question is - is the reference to object under object.valueObservers.append always correct, and points to the object, that changed it's value, or is it incorrect, and for example points to the last object that was iterated in objects.forEach or even something else?

Abstracting Core Data Fetch Request

func fetchRequestFromViewContext(nameOfEntity: NSManagedObject) {
let fetchRequest = NSFetchRequest<nameOfEntity>(entityName: "\(nameOfEntity)")
do {
let result = try? CoreDataStack.instance.viewContext.fetch(fetchRequest)
}
}
Trying to abstract the core data fetch request therefore making an argument of type managed object and passing it into the fetch request generic but is not letting me, am I on the right track on abstracting this core data fetch request?
NSFetchRequest(entityName:) takes a String but nameofEntity was given as an NSManagedObject. Change that to a String and then also pass in the type of the entity. You can use generics (the <T> below) to allow for any class conforming to NSManagedObject.
func fetchRequestFromViewContext<T: NSManagedObject>(nameOfEntity: String, type: T.Type) {
let fetchRequest = NSFetchRequest<T>(entityName: nameOfEntity)
do {
let result = try? CoreDataStack.instance.viewContext.fetch(fetchRequest)
}
}
To call this you would simply do:
fetchRequestFromViewContext(nameOfEntity: "YourEntity", type: YourEntity.self)

How can you create Results after creating records?

I have a method that should return Results, either by successfully querying, or by creating the records if they don't exist.
Something like:
class MyObject: Object {
dynamic var token = ""
static let realm = try! Realm()
class func findOrCreate(token token: String) -> Results<MyObject> {
// either it's found ...
let tokenResults = realm.objects(MyObject.self).filter("token = '\(token)'")
if !tokenResults.isEmpty {
return tokenResults
}
// ... or it's created
let newObject = MyObject()
newObject.token = token
try! realm.write {
realm.add(newObject)
}
// However, the next line results in the following error:
// 'Results<_>' cannot be constructed because it has no accessible initializers
return Results(newObject)
}
}
Maybe I should just be returning [MyObject] from this method. Is there any benefit to trying to keep it as Results instead of Array? I guess I'd lose any benefit of postponed evaluation since I'm already using isEmpty within the method, correct?
Results is an auto-updating view into underlying data in a Realm, which is why you can't construct it directly. So instead of return Results(newObject), you should return tokenResults, which will contain your newly added object, again because Results is an auto-updating view.

How can I easily duplicate/copy an existing realm object

I have a Realm Object which has several relationships, anyone has a good code snippet that generalizes a copy method, to create a duplicate in the database.
In my case i just wanted to create an object and not persist it. so segiddins's solution didn't work for me.
Swift 3
To create a clone of user object in swift just use
let newUser = User(value: oldUser);
The new user object is not persisted.
You can use the following to create a shallow copy of your object, as long as it does not have a primary key:
realm.create(ObjectType.self, withValue: existingObject)
As of now, Dec 2020, there is no proper solution for this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
I had a similar issue and found a simple workaround to get a copy of a realm object. Basically you just need to make the object conform to the NSCopying protocol, something like:
import RealmSwift
import Realm
import ObjectMapper
class Original: Object, NSCopying{
dynamic var originalId = 0
dynamic var firstName = ""
dynamic var lastName = ""
override static func primaryKey() -> String? {
return "originalId"
}
init(originalId: Int, firstName: String, lastName: String){
super.init()
self.originalId = originalId
self.firstName = firstName
self.lastName = lastName
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = Original(originalId: originalId, firstName: firstName, lastName: lastName)
return copy
}
}
then you just call the "copy()" method on the object:
class ViewController: UIViewController {
var original = Original()
override func viewDidLoad() {
super.viewDidLoad()
var myCopy = original.copy()
}
}
The nice thing about having a copy is that I can modify it without having to be in a realm write transaction. Useful when users are editing some data but didn't hit save yet or simply changed their mind.
Since this problem is still alive I post my solution which works but still needs to be improved.
I've created an extension of Object class that has this method duplicate that takes an object objOut and fills the flat properties by looking at self. When a non-flat property is found (aka a nested object) that one is skipped.
// Duplicate object with its flat properties
func duplicate(objOut: Object) -> Object {
// Mirror object type
let objectType: Mirror = Mirror(reflecting: self);
// Iterate on object properties
for child in objectType.children {
// Get label
let label = child.label!
// Handler for flat properties, skip complex objects
switch String(describing: type(of: child.value)) {
case "Double", "Int", "Int64", "String":
objOut.setValue(self.value(forKey: label)!, forKey: label)
break
default:
break
}
}
return objOut
}
Inside the Manager class for my Realms I have the method copyFromRealm() that I use to create my copies of objects.
To give you a practical example this is the structure of my Appointment class:
Appointment object
- flat properties
- one UpdateInfo object
- flat properties
- one AddressLocation object
- flat properties
- one Address object
- flat properties
- one Coordinates object
- flat properies
- a list of ExtraInfo
- each ExtraInfo object
- flat properties
This is how I've implemented the copyFromRealm() method:
// Creates copy out of realm
func copyFromRealm() -> Appointment {
// Duplicate base object properties
let cpAppointment = self.duplicate(objOut: Appointment()) as! Appointment
// Duplicate UIU object
cpAppointment.uiu = self.uiu?.duplicate(objOut: UpdateInfo()) as? UpdateInfo
// Duplicate AddressLocation object
let cpAddress = self.addressLocation?.address?.duplicate(objOut: Address()) as? Address
let cpCoordinates = self.addressLocation?.coordinates?.duplicate(objOut: Coordinates()) as? Coordinates
cpAppointment.addressLocation = self.addressLocation?.duplicate(objOut: AddressLocation()) as? AddressLocation
cpAppointment.addressLocation?.address = cpAddress
cpAppointment.addressLocation?.coordinates = cpCoordinates
// Duplicate each ExtraInfo
for other in self.others {
cpAppointment.others.append(other.duplicate(objOut: ExtraInfo()) as! ExtraInfo)
}
return cpAppointment
}
I wasn't able to find out a good and reasonable way to work with nested objects inside my duplicate() method. I thought of recursion but code complexity raised too much.
This is not optimal but works, if I'll find a way to manage also nested object I'll update this answer.
Swift 5+
Creates a Realm managed copy of an existing Realm managed object with ID
extension RLMObject {
func createManagedCopy(withID newID: String) -> RLMObject? {
let realmClass = type(of: self)
guard let realm = self.realm, let primaryKey = realmClass.primaryKey() else {
return nil
}
let shallowCopy = realmClass.init(value: self)
shallowCopy.setValue(newID, forKey: primaryKey)
do {
realm.beginWriteTransaction()
realm.add(shallowCopy)
try realm.commitWriteTransaction()
} catch {
return nil
}
return shallowCopy
}
}