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
Related
I'm developing a persistance manager using CoreData and I have the intention of making it as reusable as possible. My very first idea was to develop a function that receives a generic object as parameter and store it using CoreData. (example below)
func store<T: NSManagedObject>(object: T) {
let entityName = "\(type(of: object))"
let context = persistentContainer.viewContext
guard let auditEntity = NSEntityDescription.entity(forEntityName: entityName, in: context) else { return }
let auditToStore = Audit(entity: auditEntity, insertInto: context)
auditToStore.setValue("example value", forKey: "example key")
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
The trouble is that as far as I know, for saving data into CoreData you have to set every value of your new item to save and if the function pretends to be generic it would be very difficult to do it.
Thanks a lot.
After some research I found the answer and figured out how to create a method to store generic NSManagedObjects.
/// This method stores an object of a generic type that conforms to NSManagedObject
func insert<T: NSManagedObject>(object: T) {
let context = persistentContainer.viewContext
context.insert(object)
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
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've implemented core data in my application successfully. Everything was working fine but I got an issue. I'm using merge policy to update records.
I've two Entities name Issues and Members with relation one to many. One issue has many members.
Data comes from the server and saved in these two Entities:
Object A
Object B
Object C
This data comes first time and save in coredata. Data updates on server and when fetching second time , this data comes:
Object A
Object B
It should update and remove the Object C, but Object C still in coredata.
Please help me what i'm doing wrong here. Thanks in advance.
This is my code:
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
class func saveIssues(with json: JSON)
{
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "Issues", in: context)
let issue = NSManagedObject(entity: entity!, insertInto: context) as! Issues
issue.setValue(json["Id"].stringValue, forKey: "id")
issue.setValue(json["Name"].stringValue, forKey: "name")
issue.setValue(json["CreatedByName"].stringValue, forKey:"createdByName")
for issueMembers in json["Members"].arrayValue
{
let members = NSEntityDescription.insertNewObject(forEntityName: "Members", into: context) as! Members
members.setValue(issueMembers["FullName"].stringValue, forKey: "fullName")
members.setValue(issueMembers["PictureUrl"].stringValue, forKey: "picture_Url")
members.setValue(issueMembers["LoginId"]. stringValue, forKey: "loginId")
issue.addToIssueMembers(members)
}
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
do{
try context.save()
}catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
}
}
I investigated it a little bit and found that no one merge policy suites your needs: in case of uniqueness constraints, both NSRollbackMergePolicy and NSOverwriteMergePolicy save only old (existed on persistent store) set of objects in relationship, and both NSMergeByPropertyStoreTrumpMergePolicy and NSMergeByPropertyObjectTrumpMergePolicy save all objects in relationship the object had in two versions (on disk and in memory). So seems like you have to manage the relationship yourself...
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 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;)