Want to save Core Data but "Extensions must not contain stored properties" - ios

I have a Core Data class called "CloudPage", and I've added an extension for it in my code:
extension CloudPage:GalleryItem {
// bunch of methods defined here
func someFunc() -> Bool {
return true
}
It made sense to me to extend this class to have all model information reside here. I also thought it made sense for this class to know how to save an instance of itself, so I added my viewcontext to it:
extension CloudPage:GalleryItem {
#Environment(\.managedObjectContext) private var viewContext
// ...some code...
But now swift says "Extensions must not contain stored properties". Is there a better way to do this? I would think all saving logic should reside on this model.

As the compiler already say, it is not allow to add stored properties in Extension. CloudPage is an NSManagedObject which means it is already managed by CoreData.
Anyway this is not how you work with CoreData Objects. I'll clarify some stuff
These objects are managed/loaded in a context. There are 2 types of contexts
ViewContext (should be used for view purposes)
BackgroundContext (should be used for loading stuff from e.g. APIs in the background)
If you update objects or add new ones, these already live in said context. To persist these objects you don't have to "push" them in there anymore like you'd do it with plain SQL (UPDATE table...). You simply have to save the context like
// context is the one you loaded the objects from and worked with
context.save()
I hope this helps, i just wanted to sum it up roughly for you to get a better understanding. Don't become desperate, CoreData has its quirks and you'll get used to it. For more details read Apples Documentation of working with Core Data.

Related

swift / CoreData - Create "dummy" NSManagedObjecIDs for data models (to test things without the need of managed objects)

Say I have 2 NSManagedObjects in CoreData.
class House: NSManagedObject {}
class Location: NSManagedObject {}
I also have data model structs like this:
struct HouseModel {
var objectID: NSManagedObjectID
...
}
sruct LocationModel {
var objectID: NSManagedObjectID
...
}
For each loaded managedObject I basically use its attributes to initialize a new model struct to use for the UI and stuff (mainly collection views)
I have to have the NSManagedObjectID attribute in the structs in order to be able to make changes to the managedObject that struct belongs to. (I learned that I should use the mainViewContext only for reading while using something like persistentContainer.performBackgroundTask for writing. Thus, I need the NSManagedObjectID to load the objects on a background queue)
That's working but there is a problem with this approach:
I can't initialize one of these data models without a managed object. That's annoying when I want to create dummy data for UI testing or unit testing.
I know one solution: Create a Dummy managedObject with exactly one instance and use its objectID for stuff like that. But I don't really like this. Is there a better / more convenient way?
I mean, I would love to entirely remove the objectID attribute to keep CoreData separate from these model structs. But I don't see a way to do this. I need the connection.
For passing NSManagedObjects to a detail view for editing, it is often useful to do that on a new main queue managed object context, which simplifies your UI access and allows you to throw away the context if the user cancels changes.
But that's not what you asked.
Your problem is that you want to identify a managed object, but not use NSManagedObjectID. For this, you can use a URL property instead. NSManagedObjectID has a uriRepresentation() that returns a URL, and NSPersistentStoreCoordinator can convert a URL back into a managed object ID using managedObjectID(forURIRepresentation:). So you can store any old URL in the struct for testing purposes, and still be securely referring to managed objects in your app logic.

Should I use NSUserDefault, dictionaries, core data - or something else?

I'm having some issues with the app, that I'm making, which I thought would be a lot easier to explain with some photos, so ... :
Ofcourse the "Create New Person-button" in nr. 1 leads you to number two.
Now, I'm having issues figuring out how to save this data about the person in the "People Diary". The goal is, that when you enter a person's name, add a photo (an enable-camera feature, I will struggle with at a later time...) and add an answer to the question - then you only need to press "Save this person", and then you will be redirected to the AllPersonsInYourDiaryViewController, where there is now a new tableViewCell with this new person's name (maybe with a subtitle containing the answer and the photo shown in miniature in the cell too).
(Naturally you can then enter this cell with the data about the person too - but that comes next.)
So far in the app, I have used NSUserDefault, when allowing the user to create this specifik Diary by the Name "Antons Diary" with the specifik question and so on. But now it came to my attention, that maybe it is smarter to use something else? I tried with dictionaries, but couldn't get this to work properly.
So...: Before I spend hours and hours playing around with one of these ways, will someone smarter than me, tell me what the best approach would be?
If I can give my two cents, the first thing you have to do is to “design” how to represent a person programmatically. You can create a struct or class to do so, even though a struct is more suitable:
struct Person {
var name: String?
var answer: String?
var photo: String?
}
Then you can decide how to save the data of such an object persistently. If you want to use a database, then I would recommend using SQLite with FMDB library. It’s really easy and fast to learn how to use it, and it's also quite handy. I've used it big projects and it works smoothly. I find CoreData too complicated and an overkill based on what you need.
If you don’t want to use a database, your only other way is to save to files, but still, you’ve got options here too. If you encode (see Codable protocol in Swift), you can use NSKeyedArchiver to convert to Data object and write then to disk. If you like using dictionaries, and since the properties you’re going to have for a person are not going to be too many, you could create a dictionary by assigning the properties and their values, and then convert and save as JSON data, or even Plist files. Without any intension to do promotion here, but just to provide some additional help, if you want take a look to a library that I’ve written and that can do all these automatically for you. It’s a protocol that you have to adopt, and then you can instantly convert your struct to a dictionary, JSON or plist and save to files.
No matter which way you’re going to select, save the images as single files to documents directory, and keep their file names only stored to database/file. Based on them, you can build the path to each image (or the URL) easily when needed. Warning: Do not save the full path to the documents directory, especially if you’re testing on Simulator; paths are changing on each build. Save the file name only.
Additionally, if you’re going to use a struct like the one shown above, you could implement small but super convenient functions that will be responsible for saving, loading, or updating your data to the solution (database/file) you’ll eventually select. That way, you’ll have related stuff gathered in one place, and easily accessible (i.e., person.save()).
struct Person {
var name: String?
var answer: String?
var photo: String?
func save() {
…
}
func load() {
…
}
// More functions…
}
Lastly, avoid using UserDefaults, or at least keep just a few non-critical data there. UserDefaults are not meant to keep all data produced by your app. Most importantly, do not use it for saving sensitive data, especially passwords or other stuff like that.
I hope the above will help you make your mind.
I can give you the logic behind coreData and NSUserDefaults, but you will decide which one should be used.
CoreData is usually used as a database. you can create entities and attributes for every entity. Moreover, you can create relations between these entities.
When extracting data from coreData, you can arrange this data using NSSortDescriptor or select a specific record using NSPredicate.
So as you can see CoreData is a database.
While NSUserDefaults is usually used to save a password, username, userID... and such issues that you will regularly use in the app. NSUserDefaults gives you a direct access to the saved variables at any time. However, CoreData will take more time and lines of code to access the entity and make the query.
Now, check which method suits your case more.

How does Core data concurrency work with multithreading in Swift 3?

In my program, it uses both of
DispatchQueue.global(qos: .background)
and
self.concurrentQueue.sync(flags: .barrier)
to deal with the background multithread issues.
It is swift 3 so I use the latest way to get the childContext:
lazy var context: NSManagedObjectContext = {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.newBackgroundContext()
}()
I also enable -com.apple.CoreData.ConcurrencyDebug 1 to debug
Then the problem occurs:
1, When there's an API call and in the callback block (background thread), I need to fetch the core data, edit, then save. I tried to use self.context from the code above to call performBlockAndWait and do save inside of this block. The whole process goes fine but when I try to access my result outside of this block but inside of the callback block, the error occurs. I have also tried to get the objectId and getObjectById by both self.context and self.context.parent and the error occurs on this line. What did I do wrong and how should I do this? since I need to use the result everywhere in many different thread (not context).
2, I read a post says that I need one context per thread, then in my case, how do I determine which exact thread it is if it's a call back from API call and do I really need to do this?
3, You might ask that why do I need a privateConcurrentType, because my program has things need to be running in background thread so that it has to do it this way, (read from other post), is this right?
4, Even in my question 1, get object by passing objectId to different Context still not working in my case. Let's assume this is the proper way. How am I gonna manage passing so many objectID throughout my entire program in different thread without being super messy? To me this sounds crazy but I suppose there's a much cleaner and easier way to deal with this.
5, I have read many posts some are pretty old (before swift 3), they have to do childContext.save then parentContext.save, but since I use the code above (swift 3 only). It seems that I can do childContext.save only to make it work? Am I right?
Core data in general is not multithreading friendly. To use it on concurrent thread I can assume only bad things will happen. You may not simply manipulate managed objects outside the thread on which the context is.
As you already mentioned you need a separate context per thread which will work in most cases but by my experience you only need one background context which is read-write and a single main thread read-only context that is used for fetch result controllers or other instant fetches.
Think of a context as some in-memory module that communicates with the database (a file). Fetched entities are shared within the context but are not shared between contexts. So you can modify pretty much anything inside the context but that will not show in the database or other contexts until you save the context into the database. And if you modify the same entity on 2 contexts and then save them you will get a conflict which should be resolved by you.
All of these then make quite a mess in the code logic and so multiple contexts seem like something to avoid. What I do is create a background context and then do all of the operations on that context. Context has a method perform which will execute the code on its own thread which is not main (for background context) and this thread is serial.
So for instance when doing a smart client I will get a response from server with new entries. These are parsed on the fly and I perform a block on context to get all the corresponding objects in the database and create the ones that do not exist. Then copy the data and save the context into database.
For the UI part I do similar. Once an entry should be saved I either create or update the entity on the background context thread. Then usually do some UI stuff on completion so I have a method:
public func performBlockOnBackgroundContextAndReturnOnMain(block: #escaping (() -> Void), main: #escaping (() -> Void)) {
if let context = context {
context.perform {
block()
DispatchQueue.main.async(execute: { () -> Void in
main()
})
}
}
}
So pretty much all of the core data logic happens on a single thread which is in background. For some cases I do use a main context to get items from fetch result controller for instance; I display a list of objects with it and once user selects one of the items I refetch that item from the background context and use that one in the user interface and to modify it.
But even that may give you trouble as some properties may be loaded lazily from database so you must ensure that all the data you need will be loaded on the context and you may access them on the main thread. There is method for that but I rather use wrappers:
I have a single superclass for all the entities in the database model which include id only. So I also have a superclass wrapper which has all the logic to work with the rest of wrappers. What I am left with in the end is that for each of the subclass I need to override 2 mapping methods (from and to) managed object.
It might seem silly to create additional wrappers and to copy the data into memory from managed object but the thing is you need to do that for most of the managed objects anyway; Converting NSData to/from UIImage, NSDate to/from Date, enumerations to/from integers or strings... So in the end you are more or less just left with strings that are copied 1-to-1. Also this makes it easy to have the code that maps the response from your server in this class or any additional logic where you will have no naming conflicts with managed objects.

Fix uneccessary copy of NSManagedObject

I'm sorry the title may mislead you, since I'm not so good at English. Let me describe my problem as below (You may skip to the TL;DR version at the bottom of this question).
In Coredata, I design a Product entity. In app, I download products from a server. It return JSON string, I defragment it then save to CoreData.
After sometimes has passed, I search a product from that server again, having some interaction with server. Now, I call the online product XProduct. This product may not exist in CoreData, and I also don't want to save it to CoreData since it may not belong to this system (it come from other warehouse, not my current warehouse).
Assume this XProduct has the same properties as Product, but not belong to CoreData, the developer from before has designed another Object, the XProduct, and copy everything (the code) from Product. Wow. The another difference between these two is, XProduct has some method to interact with server, like: - (void)updateStock:(NSInteger)qty;
Now, I want to upgrade the Product properties, I'll have to update the XProduct also. And I have to use these two separately, like:
id product = anArrayContainsProducts[indexPath.row];
if ([product isKindOfClass:[XProduct class]] {
// Some stuff with the xproduct
}
else {
// Probably the same display to the cell.
}
TL;DR
Basically, I want to create a scenario like this:
Get data from server.
Check existed in CoreData.
2 == true => add to array (also may update some data from server).
2 == false => create object (contains same structure as NSManagedObject from JSON dictionary => add to array.
The object created in step 4 will never exist in CoreData.
Questions
How can I create an NSManagedObject without having it add to NSMangedObjectContext and make sure the app would run fine?
If 1 is not encouragement, please suggest me a better approach to this. I really don't like to duplicate so many codes like that.
Update
I was thinking about inheritance (XProduct : Product) but it still make XProduct the subclass of NSManagedObject, so I don't think that is a good approach.
There are a couple of possibilities that might work.
One is just to create the managed objects but not insert them into a context. When you create a managed object, the context argument is allowed to be nil. For example, calling insertNewObjectForEntityForName(_:inManagedObjectContext:) with no context. That gives you an instance of the managed object that's not going to be saved. They have the same lifetime as any other object.
Another is to use a second Core Data stack for these objects, with an in-memory persistent store. If you use NSInMemoryStoreType when adding the persistent store (instead of NSSQLiteStoreType), you get a complete, working Core Data stack. Except that when you save changes, they only get saved in memory. It's not really persistent, since it disappears when the app exits, but aside from that it's exactly the same as any other Core Data stack.
I'd probably use the second approach, especially if these objects have any relationships, but either should work.

How to use Core Data model subclasses outside a Core Data context?

I'm trying to make a weather app in Swift that will save the cities I add to Core Data, each city contain a weather object that is also saved to Core Data and other various variables.
But I soon figured out, using Core Data NSManagedObjects subclasses outside a Core Data context is close to impossible (dealing with NSNumber and similar, no custom init, forced to save them somewhere, what if I stop using Core Data tomorrow, ...).
So what's the best practice to keep using Core Data but also use models outside of its context?
My solution right now is to create a Class for each Model, so :
class City
{
var country: String?
var name: String?
// ...
}
Is the corresponding class of :
class CD_City
{
#NSManaged var country: String?
#NSManaged var name: String?
// ...
}
So I can use City anywhere and anyhow I want. But I need a func to turn a City into CD_City and opposite. So I'm really not sure I'm doing it the best way.
Also what would you recommend as a conversion method ?
(FYI I'm using MagicalRecord as a Core Data helper)
TL;DR - Don't do that or things will break.
There used to be various hacks for getting it to sort of work, but they all rely on undocumented behavior in CoreData. I would never use anything like that in code I wanted to show another human being, much less ship to customers. CoreData needs to insert proxy objects that hook into property change events on your model objects, and the only way it can reliably do that and track the original data values were is if it is responsible for creating those entities in the first place; That also makes the faulting & uniquing system work. Don't think of Core Data as an ORM, it really is an object graph management framework, and as such it is designed to be used a certain way with no easy solution to side step it safely.
If you don't want to save an NSManagedObject or a subclass of it, then you can create it with
init(entity entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
and pass nil for insertIntoManagedObjectContext this will create you an instance but it will be not be saved to the MOC.
In case you have to save it to the MOC later, you can use NSMangedObjectContext's
func insertObject(_ object: NSManagedObject)

Resources