I am new to core data in iOS and i have got around 5000 record with multiple key value .
I want to save it in core data storage in backgorund so app will not stuck. I have saved it already and the code is implemented in Appdelegate.swift class but when app run its stuck till the data load from the API and save into core data storage.
Here is my Code :-
let tempArray = NSMutableArray(array : data!)
for i in 0 ..< tempArray.count {
print(i)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
if #available(iOS 10.0, *) {
self.moc = appDelegate.persistentContainer.viewContext
}
let entity = NSEntityDescription.insertNewObject(forEntityName: "Product", into: self.moc!) as! Product
if (tempArray.object(at: i) as! NSDictionary).value(forKey: "id") != nil {
entity.setValue((tempArray.object(at: i) as! NSDictionary).value(forKey: "id") as! NSNumber , forKey: "id")
}
DispatchQueue.main.async {
do {
try self.moc?.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
i have tried with try self.moc?.save() to put in dispatch async but its giving error
You can do it with OperationQueue
let op = OperationQueue()
op.addOperation {
//Your task goes here
do {
try self.moc.save()
}
catch {
fatalError("Failure to save context: \(error)")
}
}
method 2
DispatchQueue.main.async {
do {
self.moc.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
Do like this
moc.perform {
if let a = (tempArray.object(at: i) as! NSDictionary).value(forKey: "id") {
let obj = NSManagedObject(entity: entity, insertInto: moc)
obj.setValue(a as! NSNumber , forKey: "id")
}
}
DispatchQueue.main.async {
do {
try self.moc.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
It appears that you are using a single main-thread context. ManagedObjectContexts are not thread safe so they must be run on the thread they were created on. Main-thread managedObjectContext are great for reading - because you generally display your data on the main thread - but they are terrible for writing because you would rather that done in the background. Queues or dispatch async won't fix it, because the context must be executed on the main thread.
Having a single context for the application only works if your app is super small and simple. In the past there were many setups on how to have a core-data setup with background saving and have the main context for displaying data. Since iOS 10 there is NSPersistentContainer which is a automatic setup for a great core-data stack which solves all theses issues.
If you are only supporting iOS 10+ then I strong recommend using NSPersistentContainer. This is how you use it: Treat the viewContext as readonly and never write data on the main thread. Only write data using performBackgroundTask: using the context that is given to your block of code. Don't pass managedObject into or out of the these blocks. If you need to modify a managedObject on the viewContext save its objectID and refetch in the background task. Also in you core-data setup set the (viewContext to automatically merge changes self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = YES;)
Related
I am using CoreData to insert data and fetch Data, as I have a lot of data, so I am using core data on multiple threads in order to be thread-safe.
The problem is that I am able to insert Data in CoreData, but while fetching from CoreData, the results are zero, this is happening when I kill my app and fetch the data from Database. This has something to do with NSMangedObjectContext but I am not able to figure it out.
Here is my code snippet :
class CoreDataManager {
static let sharedManager = CoreDataManager()
private init() {}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "My_Contacts")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext() {
let context = CoreDataManager.sharedManager.persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
func insertContact(id:Int, firstName : String,lastName : String,emaild : String,isFavorite : Bool,phoneNum : String,profilePic : String,sync : Bool) -> Contact? {
let managedContext = CoreDataManager.sharedManager.persistentContainer.viewContext
let privateManagedObjectContext: NSManagedObjectContext = {
//NSPrivateQueueConcurrencyType
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.parent = managedContext
return moc
}()
let entity = NSEntityDescription.entity(forEntityName: "Contact",
in: privateManagedObjectContext)!
let contact = NSManagedObject(entity: entity,
insertInto: privateManagedObjectContext)
contact.setValue(firstName, forKey: "first_name")
contact.setValue(lastName, forKey: "last_name")
contact.setValue(emaild, forKey: "email")
contact.setValue(isFavorite, forKey: "favorite")
contact.setValue(phoneNum, forKey: "phone_number")
contact.setValue(profilePic, forKey: "profile_pic")
contact.setValue(sync, forKey: "syncStatus")
contact.setValue(id, forKey: "contactId")
do {
try privateManagedObjectContext.save()
return contact as? Contact
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
return nil
}
}
func fetchAllContacts() -> [Contact]?{
let managedContext = CoreDataManager.sharedManager.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Contact")
do {
let people = try managedContext.fetch(fetchRequest)
return people as? [Contact]
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
return nil
}
}
}
so I am using core data on multiple threads in order to be thread-safe
What do you mean by this? Using multiple threads doesn't make anything thread-safe. Thread safety relates to your ability to run your code on multiple threads without problems, and it generally requires that you take a number of precautions to prevent threads from interfering with each other.
The problem is that I am able to insert Data in CoreData, but while fetching from CoreData, the results are zero, this is happening when I kill my app and fetch the data from Database. This has something to do with NSMangedObjectContext but I am not able to figure it out.
You need to understand what a managed object context is. Think of it like a temporary workspace: you can execute a fetch request to bring objects from a persistent store into a managed object context, and you can add new objects to the context, and you can manipulate the objects in a context. The changes you make in a context don't mean anything outside the context until you save the context back to the persistent store.
A few reasons you might not be seeing the objects you're adding are:
You're adding the objects and trying to read them back in different contexts.
You never save the context after you add objects.
You save the context in which you added the object, but the parent context (managed object contexts are hierarchical) is never saved.
You try to save the context after you add objects, but saving fails.
You're using the same context in multiple threads without taking care to serialize the operations on the context (which is to say, your code isn't thread-safe).
What you really should do to figure this out is to get yourself back to a state where you can store and retrieve objects reliably. Try using just one thread and make sure that your operations work. If they don't, fix that first. Next, get a solid understanding of how managed object contexts work and how to use them. Finally, read up on concurrency and Core Data.
Since you are using multiple MOC(Managed Object Context), you need to save both the contexts
You have set privateManagedObjectContext's parent to managedContext, but you are not saving managedContext
After calling privateManagedObjectContext.save(), you need to call managedContext.save() as well
I did go through other posts that dealt this issue. But I couldn't find much with regard to my problem. Hope somebody can help. My issue is...I am having a certain edited record that I want to display in my tableview. For that I want to update that entry in Core-Data also. I am not able to figure out how that can be done.
This is how I am bringing the edited data in tableview and saving in Core Data. The updation has to be done somewhere in between but I am not able to figure out exactly how and where..?
#IBAction func saveToMainEditViewController (segue:UIStoryboardSegue) {
let detailViewController = segue.source as! EditCategoriesTableViewController
let index = detailViewController.index
let modelString = detailViewController.editedModel //Edited model has the edited string
let myCategory1 = Category(context: self.context)
myCategory1.categoryName = modelString
mangObjArr[index!] = myCategory1
//Saving to CoreData
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Category", in: managedContext)
let category = NSManagedObject(entity: entity!, insertInto: managedContext)
category.setValue(myCategory1.categoryName, forKeyPath: "categoryName")
category.setValue(myCategory1.categoryId, forKey: "categoryId")
do {
try managedContext.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
Steps:
Understand basic concepts
Fetch Record
Update Record
Save context
Concepts:
This is just a crude explanation, the proper explanation is in the link below.
Though it is time consuming, please refer to the link below, it will help you understand CoreData. If you don't understand you will encounter a lot of problems later on.
Entity:
In the core data model you can create entities, these are the tables.
Managed Object:
This is the class representation of the entity
Each instance of this class would represent a single row in the table.
Managed Object Context:
Imagine managed object context like a piece of paper / scratch pad
The managed objects are created / updated / deleted on a specific managed object context.
You can save / discard the changes made to a managed object context.
Not Thread Safe:
When ever you perform anything on a managed object context, make sure you use within context.performAndWait { }. This will ensure that context operations are performed on the context's queue (thread).
Fetch and Update:
func fetch() {
let request : NSFetchRequest< Category> = Category.fetchRequest()
//Predicate builds the where clause to filter records
//This is a sample, so edit based on your requirement
request.predicate = NSPredicate(format: "categoryID = %#", argumentArray: [10])
context.performAndWait {
do {
let categories = try context.fetch(request)
//Update
for category in categories {
category.name = "aaa"
}
}
catch {
print("error = \(error)")
}
}
}
Save:
func save() {
if context.hasChanges {
context.performAndWait {
do {
context.save()
}
catch {
print("Save error: \(error)")
}
}
}
}
Reference:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/index.html
I am reading bunch of objects from core data store , when I am trying to read object from this array in later part of code Xcode asserts for wrong thread access on that statement. I have added -com.apple.CoreData.ConcurrencyDebug 1 flag to the scheme to find out issues related to core data concurrency . When I remove this flag everything works fine.
Here is how I read the data from core data store
let managedContext =
appDelegate.persistentContainer.newBackgroundContext()
//setup request to load persons ( cached contacts)
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Person")
fetchRequest.fetchBatchSize = 20
//read entties
managedContext.performAndWait {
do {
self.people = try managedContext.fetch(fetchRequest)
print("\(self.people.count) contacts retrieved from cache(CD)" )
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
And I am reading the array from other part of code as below
let tempContact = self.people[index]
var property = tempContact.value(forKey: "firstName") as! String
I get assertion on second line above. The above code resides inside a function which I have tried calling as below
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let moc = appDelegate.persistentContainer.viewContext
let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateMOC.parent = moc
privateMOC.perform {
self.updateChangedContacts()
}
self.contactsTableView.reloadData()
If I simply call updateChangedContacts directly it works , but if I have large number of contacts this operation is going to take significant time blocking main UI.
I want to avoid that , how can i approach this ?
I am working on an app written in Swift that uses Core Data. For it to work correctly, I need to delete all the objects in Core Data for a specific entity and key. I have been able to find many different questions covering deleting from core data, but as far as I can tell none of them only delete objects for a specific key.
My current code follows a similar style to the "Fetch, Delete, Repeat" method in this article. It talks about an iOS 9 updated way to do this with NSBatchDeleteRequest, but I have not discovered any way to make either of these only delete the values for a specific key.
Current Delete Code (I have tried to add a key to the object, but it throws an error about not being able to cast NSCFNumber to NSManagedObject at runtime)
getContext().delete(object.value(forKey: key) as! NSManagedObject)
Full Code Pertaining to Core Data
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
func coreDataReplaceValue(entity: String, key: String, value: Int) {
let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: entity)
do {
let searchResults = try getContext().fetch(fetchRequest)
for object in searchResults as! [NSManagedObject] {
getContext().delete(object.value(forKey: key) as! NSManagedObject)
}
} catch {
print("Error with request: \(error)")
}
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: entity, in: context)
let accelerationLevel = NSManagedObject(entity: entity!, insertInto: context)
accelerationLevel.setValue(value, forKey: key)
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
Other questions I have looked at that pertain to my question, but don't include a way to add a key:
Swift 3 Core Data Delete Object
delete core data managed object with Swift 3
This last one looked promising at first, but the code changes it into an NSManagedObject, so I don't think it holds the solution.
Deleting first object in Core Data (Swift)
Thanks for any help!
Taylor
After creating simple code to getting data from CoreData, I realised that some unknown errors are fired. I read that fetching data in background is not that simple. Unfortunately I'm new in iOS developing and only know Swift, where there are many objective-C tutorials for that.
This is my code:
func reloadData() {
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
let people = results as! [NSManagedObject]
for person in people {
collectionViewData.append(person)
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
dispatch_async(dispatch_get_main_queue()) {
self.collectionView?.reloadData()
}
}
}
How to implement this with NSPrivateQueueConcurrencyType and stuff like that in Swift?
Your code will almost certainly fail. You are accessing your main managed object context from a background thread.
From your code it seems that you do not need a background fetch. Remove all GCD (grand central dispatch) parts of your code and you should be fine.
For reference, this is how you do a background operation in Core Data:
let moc = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
moc.parentContext = <insert main context here>
moc.performBlock {
// make changes, fetch, save
}
After saving, your main context will have the changes. To update a table view, implement NSFetchedResultsController and its delegate methods to do it automatically.