Abstracting Core Data Fetch Request - swift

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)

Related

How to return value from a closure in a Struct in Swift?

I'm retrieving data from a website.
Networking works well. Data is parsed correctly from JSON.
A couple of references - In this struct:
Replies is the datamodel for the JSON
PrepareQuestions is a func which performs the parsing (I have it in an extension of the same Struct)
I'd like to have an object within this struct (downloadedData - 'Replies' is the struct with the datamodel) containing all the information downloaded, but I incur into an error due to "self being an immutable capture". Any suggestions? Thank you!
struct QuestionsManager {
var downloadedData:Replies?
func useData() {
manageQuestions(url: K.urlForRetreival, numberOfQuestions: K.numberOfSquares) { [self] (replies, error) in
if let replies = replies {
DispatchQueue.main.async {
downloadedData = replies // Here I got the error
}
}
}
}
func manageQuestions(url: String, numberOfQuestions: String, myCompletion: #escaping (Replies?, Error?)->()) {
let generatedUrl = URL(string: url + numberOfQuestions)!
let urlSession = URLSession(configuration: .default)
let task = urlSession.dataTask(with: generatedUrl) { (data, response, error) in
if error == nil {
if let fetchedData = data {
let fetchedProcessedData = prepareQuestions(data: fetchedData)
myCompletion(fetchedProcessedData, nil)
return
}
} else {
myCompletion(nil, error)
return
}
}
task.resume()
}
}
You're seeing this error because the closure captures an immutable self.
Just like primitive types (e.g. Int), structs are value-types, and Swift is built with the notion of immutability of value-types.
In other words, if you had let questionManager = QuestionManager(), you'd expect questionManager not to change. Even if it was a var, it can only mutate via direct action by the caller, e.g. questionManager.doMutatingFunc().
But, if a closure was allowed to capture self, it could modify itself at some later point. This is not allowed.
This simplest (only?) way to fix this is to turn QuestionManager into a class:
class QuestionManager {
// ...
}
struct is a value type. For value types, only methods explicitly
marked as mutating can modify the properties of self, so this is not
possible within a computed property.
If you change struct to be a class then your code compiles without
problems.
Structs are value types which means they are copied when they are
passed around.So if you change a copy you are changing only that copy,
not the original and not any other copies which might be around.If
your struct is immutable then all automatic copies resulting from
being passed by value will be the same.If you want to change it you
have to consciously do it by creating a new instance of the struct
with the modified data.
From https://stackoverflow.com/a/49253452/11734662

Cannot decode JSON using Core Data class [duplicate]

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?
I tried a very simple example:
and defined Foo myself:
import CoreData
#objc(Foo)
public class Foo: NSManagedObject, Codable {}
But when using it like this:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
The compiler failed with this errror:
super.init isn't called on all paths before returning from initializer
and the target file was the file that defined Foo
I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.
Does Core Data support Codable?
You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:
First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.
class MyManagedObject: NSManagedObject, Codable {
#NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.
required convenience init(from decoder: Decoder) throws {
}
However, the proper initializer for use with managed objects is:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Now, you can just as the decoder for the context:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
To encode data, you'll need to do something similar using the encode protocol function.
CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.
You can still use Codable with it in limited ways just as you can use NSCoding, however.
One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.
Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.
You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.
Hope this helps.
Swift 4.2:
Following casademora's solution,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
should be
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.
This prevents errors that Xcode falsely recognizes as array slice problems.
Edit: Use implicitly unwrapped optionals to remove the need to force unwrap .context every time it is being used.
As an alternative for those who would like to make use of XCode's modern approach to NSManagedObject file generation, I have created a DecoderWrapper class to expose a Decoder object which I then use within my object which conforms to a JSONDecoding protocol:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
This is probably only useful for models without any non-optional attributes, but it solves my problem of wanting to use Decodable but also manage relationships and persistence with Core Data without having to manually create all my classes / properties.
Edit: Example of it in use
If I have a json object:
let myjson = [ "name" : "Something" ]
I create the object in Core Data (force cast here for brevity):
let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
And I use the extension to have the object decode the json:
do {
try myObject.decode(json: myjson)
}
catch {
// handle any error
}
Now myObject.name is "Something"

Save a Struct to Core Data (NOT a CLASS) [duplicate]

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?
I tried a very simple example:
and defined Foo myself:
import CoreData
#objc(Foo)
public class Foo: NSManagedObject, Codable {}
But when using it like this:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
The compiler failed with this errror:
super.init isn't called on all paths before returning from initializer
and the target file was the file that defined Foo
I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.
Does Core Data support Codable?
You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:
First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.
class MyManagedObject: NSManagedObject, Codable {
#NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.
required convenience init(from decoder: Decoder) throws {
}
However, the proper initializer for use with managed objects is:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Now, you can just as the decoder for the context:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
To encode data, you'll need to do something similar using the encode protocol function.
CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.
You can still use Codable with it in limited ways just as you can use NSCoding, however.
One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.
Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.
You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.
Hope this helps.
Swift 4.2:
Following casademora's solution,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
should be
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.
This prevents errors that Xcode falsely recognizes as array slice problems.
Edit: Use implicitly unwrapped optionals to remove the need to force unwrap .context every time it is being used.
As an alternative for those who would like to make use of XCode's modern approach to NSManagedObject file generation, I have created a DecoderWrapper class to expose a Decoder object which I then use within my object which conforms to a JSONDecoding protocol:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
This is probably only useful for models without any non-optional attributes, but it solves my problem of wanting to use Decodable but also manage relationships and persistence with Core Data without having to manually create all my classes / properties.
Edit: Example of it in use
If I have a json object:
let myjson = [ "name" : "Something" ]
I create the object in Core Data (force cast here for brevity):
let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
And I use the extension to have the object decode the json:
do {
try myObject.decode(json: myjson)
}
catch {
// handle any error
}
Now myObject.name is "Something"

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

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)

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
}
}