There is 1 thing I don't completely understand about working with CoreData (and I can't find a good answer to my question): how do you use CoreData in an app with multiple UIViewcontrollers ?
At the moment I was playing around with core data in multiple controllers and I typed this in each VC:
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let entity = NSEntityDescription.entityForName("EntityName", inManagedObjectContext: context)!
let entityName = EntityName(entity: entity, insertIntoManagedObjectContext: context)
But I don't think this is the best practice for working with CoreData on multiple controllers (side question: does this have an impact on my app's preformance?). What is the most preferred way?
Do I create a managed object context in each viewcontroller?
(Lots of copy-paste, like I'm doing now)
Do I create a singleton for the creation of CoreData ? I've read somewhere that this was not the preferred way
Do I pass the managed object context to the next controller using
segues (or set a public context variable in the app delegate's function '*didFinishLaunchingWithOptions*')?
Something else?
Thanks in advance!
3-> Setting a public main queue MOC is a good practice. Because main queue is a serial queue.
1-> Using the main queue MOC only for fetching and creating child MOC for each editing is a good practice. Because editing MOCs that can be fetched may cause conflicts.
2-> You can try creating a singleton instance for getting the shared main queue MOC and define some functions that create child context for editing.
4-> But actually theres no best practice for abstract situations.
I think so.
What you're trying to do is to avoid code duplication. Therefore just bring the code into one single function and place it somewhere. It could be an extension on UIViewControler or AppDelegate or just a helper function
This is how an extension on UIViewController would look like:
extension UIViewController
{
func doCoreDataWork()
{
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let entity = NSEntityDescription.entityForName("EntityName", inManagedObjectContext: context)!
let entityName = EntityName(entity: entity, insertIntoManagedObjectContext: context)
}
}
then call doCoreDataWork() from any view controller.
And no, it has nothing to do with performance
Related
i'd could use your advice on this one
i restructured my whole app, to use coreData so i'd be able to save the Main Class of my app: "FooBar"
i also transfered my complete business logic into that NSManagedObject subclass FooBar()
that's why i needed to to use some kind of convenience init, to start my own methods for calculating values and so on
here's my class definition:
Import Foundation
import CoreData
import UIKit
#objc(FooBar)
public class FooBar: NSManagedObject {
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext // used for other CD interactions inside this class
var member1 = Int()
var member2 = Double()
var member3:Double = 0
var fooPack:[FooPacks] = []
...
convenience init( param1: Int16
, param2: String?
, param3: Float
, param4: String?
, param5: Float
, fooPack: NSSet
, entity: NSEntityDescription
, context: NSManagedObjectContext?
) {
self.init(entity: entity, insertInto: context)
self.param1 = param1
self.param2 = param2
self.param3 = param3
self.param4 = param4
self.param5 = param5
self.addToUsedfooBarPacks(fooBarPack)
self.build()
}
func build() {
// i do something
}
func method1() {
// i do something
}
when i initialize my FooBar class inside a viewController i do this by:
self.fooBar = FooBar.init(param1: var1,
param2: var2,
iparam3: var3,
param4: var4,
param5: var5,
fooPack: var6,
entity: FooEntity,
context: context)
when ever i modify any instance of this class i do this by:
self.fooBar.member2 = newValue
self.fooBar.build()
and later at a specific point (in multiple VC's) i then call to save that Instance to CoreData:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
try context.save()
}catch{
print("\(error)")
}
with this approach i often have to deal with bugs and unexpected behaviours like:
the do block gets executed successfully, but the object has NOT been saved!?
i often hand over FooBar instances to a different ViewController and i try to save them there,
but this does not seem to be the right way to do update previous fetched objects
in my opinion, this has to do with the way i handle the context variables..
and exactly this is why i am reaching out for your advice..
What is the optimal way to handle such a situation, where i want to modify and save instances within multiple different ViewControllers?
i already learned a lot new stuff about CoreData in the last months, but i seem to miss something important here..
please help me out guys :)
You're using persistentContainer, it helps you setup coredata stack. persistentContainer has two types of context: viewContext and background contexts
With this setup we always use viewContext to fetch data to display on UI.
And use a background context to make changes, update storage...
What is the kind of context you passed in init method?
You should execute context.save() on exactly the context that you used to make changes (you passed to init). In this case you shouldn't pass viewContext to init and use viewContext to call save()
How to update and sync changes between multiple ViewControllers?
The view controller that makes changes should call save as well. And you can use delegation pattern to delegate/broadcast changes to other view controllers...
I've recently been learning about Core Data and specifically how to do inserts with a large number of objects. After learning how to do this and solving a memory leak problem that I met, I wrote the Q&A Memory leak with large Core Data batch insert in Swift.
After changing NSManagedObjectContext from a class property to a local variable and also saving inserts in batches rather than one at a time, it worked a lot better. The memory problem cleared up and the speed improved.
The code I posted in my answer was
let batchSize = 1000
// do some sort of loop for each batch of data to insert
while (thereAreStillMoreObjectsToAdd) {
// get the Managed Object Context
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil // if you don't need to undo anything
// get the next 1000 or so data items that you want to insert
let array = nextBatch(batchSize) // your own implementation
// insert everything in this batch
for item in array {
// parse the array item or do whatever you need to get the entity attributes for the new object you are going to insert
// ...
// insert the new object
let newObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
// save the context
do {
try managedObjectContext.save()
} catch {
print(error)
}
}
This method seems to be working well for me. The reason I am asking a question here, though, is two people (who know a lot more about iOS than I do) made comments that I don't understand.
#Mundi said:
It seems in your code you are using the same managed object context,
not a new one.
#MartinR also said:
... the "usual" implementation is a lazy property which creates the
context once for the lifetime of the app. In that case you are reusing
the same context as Mundi said.
Now I don't understand. Are they saying I am using the same managed object context or I should use the same managed object context? If I am using the same one, how is it that I create a new one on each while loop? Or if I should be using just one global context, how do I do it without causing memory leaks?
Previously, I had declared the context in my View Controller, initialized it in viewDidLoad, passed it as a parameter to my utility class doing the inserts, and just used it for everything. After discovering the big memory leak is when I started just creating the context locally.
One of the other reasons I started creating the contexts locally is because the documentation said:
First, you should typically create a separate managed object context
for the import, and set its undo manager to nil. (Contexts are not
particularly expensive to create, so if you cache your persistent
store coordinator you can use different contexts for different working
sets or distinct operations.)
What is the standard way to use NSManagedObjectContext?
Now I don't understand. Are they saying I am using the same managed
object context or I should use the same managed object context? If I
am using the same one, how is it that I create a new one on each while
loop? Or if I should be using just one global context, how do I do it
without causing memory leaks?
Let's look at the first part of your code...
while (thereAreStillMoreObjectsToAdd) {
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
managedObjectContext.undoManager = nil
Now, since it appears you are keeping your MOC in the App Delegate, it's likely that you are using the template-generated Core Data access code. Even if you are not, it is highly unlikely that your managedObjectContext access method is returning a new MOC each time it is called.
Your managedObjectContext variable is merely a reference to the MOC that is living in the App Delegate. Thus, each time through the loop, you are merely making a copy of the reference. The object being referenced is the exact same object each time through the loop.
Thus, I think they are saying that you are not using separate contexts, and I think they are right. Instead, you are using a new reference to the same context each time through the loop.
Now, your next set of questions have to do with performance. Your other post references some good content. Go back and look at it again.
What they are saying is that if you want to do a big import, you should create a separate context, specifically for the import (Objective C since I have not yet made time to learn Swift).
NSManagedObjectContext moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
You would then attach that MOC to the Persistent Store Coordinator. Using performBlock you would then, in a separate thread, import your objects.
The batching concept is correct. You should keep that. However, you should wrap each batch in an auto release pool. I know you can do it in swift... I'm just not sure if this is the exact syntax, but I think it's close...
autoreleasepool {
for item in array {
let newObject = NSEntityDescription.insertNewObjectForEntityForName ...
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
In pseudo-code, it would all look something like this...
moc = createNewMOCWithPrivateQueueConcurrencyAndAttachDirectlyToPSC()
moc.performBlock {
while(true) {
autoreleasepool {
objects = getNextBatchOfObjects()
if (!objects) { break }
foreach (obj : objects) {
insertObjectIntoMoc(obj, moc)
}
}
moc.save()
moc.reset()
}
}
If someone wants to turn that pseudo-code into swift, it's fine by me.
The autorelease pool ensures that any objects autoreleased as a result of creating your new objects are released at the end of each batch. Once the objects are released, the MOC should have the only reference to objects in the MOC, and once the save happens, the MOC should be empty.
The trick is to make sure that all object created as part of the batch (including those representing the imported data and the managed objects themselves) are all created inside the autorelease pool.
If you do other stuff, like fetching to check for duplicates, or have complex relationships, then it is possible that the MOC may not be entirely empty.
Thus, you may want to add the swift equivalent of [moc reset] after the save to ensure that the MOC is indeed empty.
This is a supplemental answer to #JodyHagins' answer. I am providing a Swift implementation of the pseudocode that was provided there.
let managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator // or wherever your coordinator is
managedObjectContext.performBlock { // runs asynchronously
while(true) { // loop through each batch of inserts
autoreleasepool {
let array: Array<MyManagedObject>? = getNextBatchOfObjects()
if array == nil { break }
for item in array! {
let newEntityObject = NSEntityDescription.insertNewObjectForEntityForName("MyEntity", inManagedObjectContext: managedObjectContext) as! MyManagedObject
newObject.attribute1 = item.whatever
newObject.attribute2 = item.whoever
newObject.attribute3 = item.whenever
}
}
// only save once per batch insert
do {
try managedObjectContext.save()
} catch {
print(error)
}
managedObjectContext.reset()
}
}
These are some more resources that helped me to further understand how the Core Data stack works:
Core Data Stack in Swift – Demystified
My Core Data Stack
I've seen many tutorials and they really help me with understand parent-child managed object context and other things related to this. I am ready to start using it in my app but I have a question. Why nobody use singleton for keeping main managed object context. I guess it would be much better to extract Core Data related objects from AppDelegate and set it to own class right? Something like in this Tutorial at raywenderlich.com. But they still instantiate CoreDataStack class (no problem with this, singleton must be instantiate too) and when it's need they set managedObjectContext in prepareForSegue (and set it to first view controller from AppDelegate). Why not to remove this need and just use singleton CoreDataStack and have possible to use managedObjectContext in each controller if needed?
Second and bonus question: I think it's better to have less code in controller and more in other classes. I think it helps with readability. So what if I move this code from controller and set it for example to CoreDataStack class or some other class that helps with Core Data requests and responses:
func surfJournalFetchRequest() -> NSFetchRequest {
let fetchRequest =
NSFetchRequest(entityName: "JournalEntry")
fetchRequest.fetchBatchSize = 20
let sortDescriptor =
NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
return fetchRequest
}
I know it's possible but is it better? If you get app codes from me would it be better if in controller it would be one line CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?
And what about if I take this code and insert it to singleton and created function with closure? I would created child managed context in singleton and do needed operations in there and in controller I would just changed UI:
func exportCSVFile() {
navigationItem.leftBarButtonItem = activityIndicatorBarButtonItem()
let privateContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = coreDataStack.context.persistentStoreCoordinator
privateContext.performBlock { () -> Void in
var fetchRequestError:NSError? = nil
let results = privateContext.executeFetchRequest(self.surfJournalFetchRequest(), error: &fetchRequestError)
if results == nil {
println("ERROR: \(fetchRequestError)")
}
let exportFilePath = NSTemporaryDirectory() + "export.csv"
let exportFileURL = NSURL(fileURLWithPath: exportFilePath)!
NSFileManager.defaultManager().createFileAtPath(exportFilePath, contents: NSData(), attributes: nil)
var fileHandleError: NSError? = nil
let fileHandle = NSFileHandle(forWritingToURL: exportFileURL, error: &fileHandleError)
if let fileHandle = fileHandle {
for object in results! {
let journalEntry = object as! JournalEntry
fileHandle.seekToEndOfFile()
let csvData = journalEntry.csv().dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
fileHandle.writeData(csvData!)
}
fileHandle.closeFile()
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem =
self.exportBarButtonItem()
println("Export Path: \(exportFilePath)")
self.showExportFinishedAlertView(exportFilePath)
})
} else {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.navigationItem.leftBarButtonItem = self.exportBarButtonItem()
println("ERROR: \(fileHandleError)")
})
}
}
}
I just want to be sure that my aproach would be okay and would be better than original. Thanks
I built my first core data app with a singleton pattern. It seemed logical for me because there is only one core data stack anyway. I was very wrong, the singleton pattern turned into a big mess quickly. I added more and more code to bend the singleton stack to something that works. In the end I gave up and I invested the time to replace the singleton mess with dependency injection.
Here are some of the problems I encountered before I dumped the singleton:
Since the app kept important data, my users requested a backup functionality. To restore from a backup I switched the sqlite file and then I would just create a new Core Data stack. Doing this in a clean way is next to impossible if you use a pull-pattern to get the managedObjectContext from a singleton. So my way to switch the Core Data stack was to tell the user that they have to restart the app. Followed by an exit(). Not the most elegant way to handle this.
After Apple added childContexts I decided to get rid of undo managers and context rollbacks, because that never worked 100% for me. But changing my editing viewControllers so they use child contexts which are discarded when the user hits cancel, was an incredible painful act because I now had a mix of singleton contexts and viewController local contexts in one viewController.
For editing the targets of relationships I had editViewControllers inside editViewController. Because I created the edit context inside the edit viewControllers I ended up saving data to the main context that shouldn't have been saved. It's a bit complicated to explain, but the second viewController saved stuff like new objects to the main context even if the user in the outer edit viewController hit cancel. Which always lead to orphaned objects. So I added more code to bend the singleton in a way that would make it less of a singleton.
I also had a CSV import function and I wanted to preview the data to the user before they press "Import". I build a totally new infrastructure for that. First I parsed the CSV into a data structure that basically duplicated my core data classes. Then I build a viewController to display these non core data classes, with even more code duplication. I would only start to create core data objects when the user pressed import.
After I got rid of the singleton pattern I could reuse the existing data display viewController. I would just give it a different context, in this case an in-memory context that contained the data that will be imported. Much cleaner, less duplicated code.
I guess some of these problems were not really the singletons fault. I was just very inexperienced.
But I still would strongly recommend against singleton core data.
would be one line CoreDataStack.fetchRequest("JournalEntry", sortedKey: "date")?
You don't need a singleton for this. Stuff like this should be in the NSManagedObject subclass you create for JournalEntry.
And what about if I take this code and insert it to singleton and created function with closure? I would created child managed context in singleton and do needed operations in there and in controller I would just changed UI:
And why don't you create a method that doesn't require internal state at all?
class func export(#context: NSManagedObjectContext, toCSVAtPath path: String,
progress: ((current: Int, totalCount: Int) -> Void)?,
completion: ((success: Bool, error: NSError?) -> Void)?) {
Much more flexible.
I've tried Googling and searching SO, thinking it would be quite easy to find but I'm surprised there were little to no examples of my problem in Swift.
I have several threads and from what I've read, it's best to have a separate managed object context for each thread. If I want to access an object from another context, I should pass around the objectID.
My question is, how should I pass the objectID to the context in a new thread?
Below is how the construction of my situation is:
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Birthdays has a one to one relationship with Persons. If I execute this code it will give me an error saying:
Illegal attempt to establish a relationship 'person1' between objects in different contexts
Obviously because they are in separated contexts. So I tried to get the objectID of person1 by let personsObjectID = person1.objectID, but I don't know how I should be using it to pass it to the other thread. Any help is appreciated.
Seems like the only thing I needed to do was to add:
let person = background.objectWithID(orders.objectID as! Persons
If I've understood it correctly, objectWithID fetches the object from the store and puts it in the context, returning the managed object. It would be nice if someone could verify this if it's true.
func doSomething(context: NSManagedObjectContext) {
let person = Persons(context: context) // NSManagedObject
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
let background = (UIApplication.sharedApplication().delegate as! AppDelegate).cdh.backgroundContext!
let person = background.objectWithID(orders.objectID as! Persons
Birthdays(context: background, person) // NSManagedObject
saveContext(background)
}
}
Hendrik, you are correct, you should use objectWithID to instantiate the NSManagedObject on the required context.
I'm saving objects to my core data, and now I need to save data to two different entities at once.
Following my own logic, if I normally save my data like this (which works perfectly):
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let contxt: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("MainTable", inManagedObjectContext: contxt)
var newMessage = SecondModel(entity: en!, insertIntoManagedObjectContext: contxt)
newMessage.receiver = object["Receiver"] as String
contxt.save(nil)
I should be able to save to another entity by creating another variable with my different entity like this:
let enet = NSEntityDescription.entityForName("AlreadyRec", inManagedObjectContext: contxt)
var secondMessage = ThirdModel(entity: enet!, insertIntoManagedObjectContext: contxt)
This gives me the error saying the "ThirdModel has no available initializers, and I don't know what this means or how to solve this issue. Please help me solve this problem.
Any suggestions on how to proceed would be highly appreciated.
Your context covers all of Core Data. Just save at the end.
create / edit entity1
create / edit entity2
save
Some advice: get away from your nondescript naming conventions. Don't call your entity MainTable - that does not even make sense in the SQL world! To my horror I noticed that your managed object model subclass is not called MainTable but SecondModel.
If your data describes widgets, you should call your entity and its subclass Widget. If it is something abstract, such as programming habits, you could call it Habit. Get the point?