I'm working on an existing project, using CoreData, that:
Adds many many items from different CoreData Entity types, after receiving them from web service, but it blocks the UI Thread for many seconds, even if I use it in another thread.
Please help me, is there any way to prevent CoreData from blocking UI with minimum changes as the project is almost completed?
I'm new to CoreData and I don't have enough time to study the doc or reprogram the source code, unfortunately.
My DataController:
class DataController {
var managedObjectContext: NSManagedObjectContext
let modelName = "something"
init(closure:()->()) {
guard let modelURL = NSBundle.mainBundle().URLForResource(modelName, withExtension: "momd"),
let managedObjectModel = NSManagedObjectModel.init(contentsOfURL: modelURL)
else {
fatalError("DataController - COULD NOT INIT MANAGED OBJECT MODEL")
}
let coordinator = NSPersistentStoreCoordinator.init(managedObjectModel: managedObjectModel)
managedObjectContext = {
let parentContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
parentContext.persistentStoreCoordinator = coordinator
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.parentContext = parentContext
return managedObjectContext
}()
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true,
NSSQLitePragmasOption: ["journal_mode": "DELETE"]
]
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last
let storeURL = NSURL.init(string: "something", relativeToURL: documentsURL)
do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options)
dispatch_async(dispatch_get_main_queue()) {
closure()
}
}
catch let error as NSError {
fatalError("DataController - COULD NOT INIT SQLITE STORE: \(error.localizedDescription)")
}
}
}
func save(moc: NSManagedObjectContext? = nil) {
var managedObjectContext = moc
if moc == nil {
managedObjectContext = self.managedObjectContext
}
managedObjectContext!.performBlockAndWait {
if managedObjectContext!.hasChanges {
do {
try managedObjectContext!.save()
} catch {
print("ERROR saving context \(managedObjectContext!.description) - \(error)")
}
}
if let parentContext = managedObjectContext!.parentContext {
self.save(parentContext)
}
}
}
}
My classes are something like:
class MOCity: NSManagedObject {
#NSManaged var id: NSNumber?
#NSManaged var name: String?
// Insert code here to add functionality to your managed object subclass
class func save(id: NSNumber, json: JSON, managedObjectContext: NSManagedObjectContext) {
guard let newObject = MOCity.getById(id, create: true, managedObjectContext: managedObjectContext) else {
fatalError("City - NO OBJECT")
}
newObject.id <-- json["Id"]
newObject.name <-- json["Name"]
}
internal class func getById(id: NSNumber, create: Bool = false, managedObjectContext: NSManagedObjectContext) -> MOCity? {
let fetchRequest = NSFetchRequest(entityName: "City")
fetchRequest.predicate = NSPredicate(format: "id = \(id)")
do {
guard let fetchResults = try managedObjectContext.executeFetchRequest(fetchRequest) as? [MOCity] else {
fatalError("city - NO FETCH RESULTS")
}
if fetchResults.count > 0 {
return fetchResults.last
}
else if fetchResults.count == 0 {
if create {
guard let object = NSEntityDescription.insertNewObjectForEntityForName("City", inManagedObjectContext: managedObjectContext) as? MOCity else {
fatalError("getCity - COULD NOT CREATE OBJECT")
}
return object
} else {
return nil
}
}
}
catch let error as NSError {
...
}
return nil
}
}
And my web service class:
Alamofire.request(.POST, url,parameters:data).validate().responseJSON(completionHandler: {response in
switch response.result {
case .Success:
if let jsonData = response.result.value {
if let array = jsonData as? NSArray {
for arrayItem in array {
MOCity.save(arrayItem["Id"] as! NSNumber, json: JSON(arrayItem as! NSDictionary)!, managedObjectContext: self.dataController.managedObjectContext)
}
self.dataController.save()
}
}
break
case .Failure(let error):
...
}
})
One of the issue could be connected for the QOS level.
QOS_CLASS_USER_INITIATED: The user initiated class represents tasks
that are initiated from the UI and can be performed asynchronously. It
should be used when the user is waiting for immediate results, and for
tasks required to continue user interaction.
Try to use QOS_CLASS_BACKGROUND.
I would suggest two things. First, save the private queue context inside a performBlock function just as you do the main queue context. Second, set breakpoints at different places, such as before and after saves, to check which thread is calling that code (using NSThread's thread checking methods). That may enlighten you as to what code is being executed from which thread. Hope that helps steer you in the right direction.
Related
I have developed a framework that has its own database based on Core Data. The problem is that sometimes after an update the data changes but does not persist. And when closing and opening the application the data remains the old ones. The Core Data stack to initialize it and do the save operations is as follows:
import CoreData
// MARK: - TypeAliases
typealias BatchTask=(_ workerContext: NSManagedObjectContext) -> ()
// MARK: - Notifications
enum CoreDataStackNotifications : String{
case ImportingTaskDidFinish = "ImportingTaskDidFinish"
}
// MARK: - Main
internal struct CoreDataStack {
// MARK: - Properties
private let model : NSManagedObjectModel
private let coordinator : NSPersistentStoreCoordinator
private let modelURL : URL
private let dbURL : URL
private let persistingContext : NSManagedObjectContext
private let backgroundContext : NSManagedObjectContext
public let context : NSManagedObjectContext
// MARK: - Initializers
public init?(modelName: String){
guard let modelURL = Bundle(identifier: "com.my.framework")!.url(forResource: modelName, withExtension: "momd") else {
MyLogger.printLog("Unable to find \(modelName)in the main bundle")
return nil}
self.modelURL = modelURL
// Try to create the model from the URL
guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
MyLogger.printLog("unable to create a model from \(modelURL)")
return nil
}
self.model = model
// Create the store coordinator
coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
// Create a persistingContext (private queue) and a child one (main queue)
// create a context and add connect it to the coordinator
persistingContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
persistingContext.name = "Persisting"
persistingContext.persistentStoreCoordinator = coordinator
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.parent = persistingContext
context.name = "Main"
// Create a background context child of main context
backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
backgroundContext.parent = context
backgroundContext.name = "Background"
// Add a SQLite store located in the documents folder
let fm = FileManager.default
guard let docUrl = fm.urls(for: .documentDirectory, in: .userDomainMask).first else {
MyLogger.printLog("Unable to reach the documents folder")
return nil
}
self.dbURL = docUrl.appendingPathComponent("modelMyFrameworkSDK.sqlite")
do {
try addStoreTo(coordinator: coordinator,
storeType: NSSQLiteStoreType,
configuration: nil,
storeURL: NSURL(fileURLWithPath: dbURL.absoluteString),
options: nil)
} catch {
MyLogger.printLog("unable to add store at \(dbURL)")
}
}
// MARK: - Utils
func addStoreTo(coordinator coord : NSPersistentStoreCoordinator,
storeType: String,
configuration: String?,
storeURL: NSURL,
options : [NSObject : AnyObject]?) throws{
try coord.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: dbURL, options: nil)
}
}
// MARK: - Removing data
extension CoreDataStack {
func dropAllData() throws{
// delete all the objects in the db. This won't delete the files, it will
// just leave empty tables.
try coordinator.destroyPersistentStore(at: dbURL, ofType:NSSQLiteStoreType , options: nil)
try addStoreTo(coordinator: self.coordinator, storeType: NSSQLiteStoreType, configuration: nil, storeURL: NSURL(fileURLWithPath: dbURL.absoluteString), options: nil)
}
}
// MARK: - Batch processing in the background
extension CoreDataStack{
func performBackgroundBatchOperation(batch: #escaping BatchTask){
backgroundContext.perform() {
batch(self.backgroundContext)
// Save it to the parent context, so normal saving
// can work
do{
try self.backgroundContext.save()
}catch{
fatalError("Error while saving backgroundContext: \(error)")
}
}
}
}
// MARK: - Heavy processing in the background.
// Use this if importing a gazillion objects.
extension CoreDataStack {
func performBackgroundImportingBatchOperation(batch: #escaping BatchTask) {
// Create temp coordinator
let tmpCoord = NSPersistentStoreCoordinator(managedObjectModel: self.model)
do{
try addStoreTo(coordinator: tmpCoord, storeType: NSSQLiteStoreType, configuration: nil, storeURL: NSURL(fileURLWithPath: dbURL.absoluteString), options: nil)
}catch{
fatalError("Error adding a SQLite Store: \(error)")
}
// Create temp context
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.name = "Importer"
moc.persistentStoreCoordinator = tmpCoord
// Run the batch task, save the contents of the moc & notify
moc.perform() {
batch(moc)
do {
try moc.save()
}catch{
fatalError("Error saving importer moc: \(moc)")
}
let nc = NotificationCenter.default
let n = Notification(name:Notification.Name(rawValue: CoreDataStackNotifications.ImportingTaskDidFinish.rawValue),
object: nil)
nc.post(n)
}
}
}
// MARK: - Save
extension CoreDataStack {
public func save() {
// We call this synchronously, but it's a very fast
// operation (it doesn't hit the disk). We need to know
// when it ends so we can call the next save (on the persisting
// context). This last one might take some time and is done
// in a background queue
context.performAndWait(){
if self.context.hasChanges{
do{
try self.context.save()
}catch{
fatalError("Error while saving main context: \(error)")
}
}
if self.persistingContext.hasChanges{
// now we save in the background
self.persistingContext.perform(){
do{
try self.persistingContext.save()
}catch{
fatalError("Error while saving persisting context: \(error)")
}
}
}
if self.backgroundContext.hasChanges {
self.backgroundContext.perform(){
do{
try self.backgroundContext.save()
}catch{
fatalError("Error while saving persisting context: \(error)")
}
}
}
}
}
func autoSave(delayInSeconds : Int){
if delayInSeconds > 0 {
MyLogger.printLog("Autosaving")
save()
DispatchQueue.main.asyncAfter(deadline: .now() + Double(delayInSeconds)) {
self.autoSave(delayInSeconds: delayInSeconds)
}
}
}
}
The creation of the objects is as shown below:
public static func myObject(withData1 data1: String, data2: Bool, context: NSManagedObjectContext) -> MyObject {
let myObject = NSEntityDescription.insertNewObject(forEntityName: MyObject.entityName(), into: context) as! MyObject
myObject.data1 = data1
myObject.data2 = data2
myObject.creationDate = Date()
myObject.modificationDate = Date()
return myObject
}
And the update of the objects is as you can see below:
public func update(withJSONObject JSONObject: [String: Any]) {
guard let data1 = JSONObject["data1"] as? String else { return }
guard let data2 = JSONObject["data2"] as? Bool else { return }
self.data1 = data1
self.data2 = data2
self.modificationDate = Date()
}
The creation and updating of the objects is done in the return callback of a call to the server.
I have tried executing these actions in the main thread in case that was the problem. And I tried to use context.perform {}
context.perform {
object.update(withJSONObject: json)
coreDataStack.save()
}
I have the following coredata singleton class
in my appdelegate i try to update the data from json
but in my app when it launches i get different error messages regarding thread
like
Main Thread Checker: UI API called
or observer of NSManagedObjectContextObjectsDidChangeNotification
or Incorrect guard value
What is the problem and what changes should i make for it to work?
thanks
import CoreData
import Foundation
class CoreDataStack {
static let sharedManager = CoreDataStack()
private init() {} // Prevent clients from creating another instance.
//This is the name of your coreData Database
static let modelName = "myDB"
static let FirstLaunchKey = "firstLaunch"
lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = Bundle.main.url(forResource: CoreDataStack.modelName, withExtension: "momd")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
lazy var applicationDocumentsDirectory: URL = {
return NSPersistentContainer.defaultDirectoryURL()
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let persistentStoreURL = self.applicationDocumentsDirectory.appendingPathComponent(CoreDataStack.modelName + ".sqlite")
do {
// let dict = [NSSQLitePragmasOption: ["journal_mode":"DELETE"]]
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: persistentStoreURL,
options: [NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: false])
} catch {
fatalError("Persistent store error! \(error)")
}
return coordinator
}()
fileprivate lazy var saveManagedObjectContext: NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.persistentStoreCoordinator = self.persistentStoreCoordinator
return moc
}()
#objc lazy var mainObjectContext: NSManagedObjectContext = {
let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext.parent = self.saveManagedObjectContext
return managedObjectContext
}()
func saveMainContext() {
guard mainObjectContext.hasChanges || saveManagedObjectContext.hasChanges else {
return
}
mainObjectContext.performAndWait() {
// print("save performAndWait")
do {
try self.mainObjectContext.save()
} catch {
fatalError("Error saving main managed object context! \(error)")
}
}
saveManagedObjectContext.perform() {
// print("save simple")
do {
try self.saveManagedObjectContext.save()
} catch {
fatalError("Error saving private managed object context! \(error)")
}
}
}
func removeDatabaseForVersion(version:String){
let previouslyVersion = UserDefaults.standard.bool(forKey: version)
if !previouslyVersion {
UserDefaults.standard.set(true, forKey: version)
// Default directory where the CoreDataStack will store its files
let directory = NSPersistentContainer.defaultDirectoryURL()
let url = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite")
let shmURL = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite-shm")
let walURL = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite-wal")
_ = try? FileManager.default.removeItem(at: url)
_ = try? FileManager.default.removeItem(at: shmURL)
_ = try? FileManager.default.removeItem(at: walURL)
}
}
}
in my appdelegate:
UpdateDbClass.updateDatabase(entityName: DbTable.VehiclesEntity.rawValue, completionHandler: {
print(" DB updated delegate")
})
in updatedb class:
import UIKit
import Alamofire
import CoreData
enum LoaderError:String{
case
JsonFailed,
PathFailed,
NoEntityDescription,
UnknownError
}
enum DbTable:String{
case
VehiclesEntity,
PhotosEntity,
ModelsEntity,
NewsEntity,
StylesEntity
}
class UpdateDbClass {
static func updateDatabase(entityName:String,completionHandler: #escaping () -> Void){
var url = URL(string: UrlRepository.VehiclesJsonUrl!)!
var table = ""
switch entityName {
case DbTable.VehiclesEntity.rawValue:
table = "Vehicles"
url = URL(string: UrlRepository.VehiclesJsonUrl!)!
case DbTable.PhotosEntity.rawValue:
table = "Photos"
url = URL(string: UrlRepository.PhotosJsonUrl!)!
table = "Styles"
url = Bundle.main.url(forResource: "Styles", withExtension: "json")!
// url = URL(string: UrlRepository.NewsJsonUrl!)!
default:
break
}
let uuid = UUID().uuidString
let parameters: Parameters = [
"id": uuid
]
let queue = DispatchQueue(label: "com.my.test", qos: .background, attributes: .concurrent)
AF.request(url, method: .get, parameters: parameters, encoding: URLEncoding(destination: .queryString), headers: nil).responseJSON(queue:queue){ response in
switch response.result {
case let .success(value):
if let items = value as? [[String: Any]] {
var itemsArray:[Int32] = []
for item in items{
if let id = item["id"] as? Int32{
itemsArray.append(id)
}
}
guard let entity = NSEntityDescription.entity(forEntityName: table, in:(CoreDataStack.sharedManager.mainObjectContext)) else {
fatalError("Could not find entity descriptions!")
}
switch entityName {
case DbTable.StylesEntity.rawValue: //Styles
checkDeletedRecords(jsonItems: itemsArray,table: table)
for item in items{
guard let id = item["id"] as? Int32 else {return}
//Check if not exists
if !CheckIfExists(id: id,table:table){
print("id \(id) does not exist")
//Insert Record
let object = Styles(entity: entity, insertInto: CoreDataStack.sharedManager.mainObjectContext)
object.setValue(item["id"], forKey: "id")
object.setValue(item["style"] as! String, forKey: "style")
object.setValue(item["image"] as! String, forKey: "image")
CoreDataStack.sharedManager.saveMainContext()
}
else{ //Update Record
// print("id \(item["id"]) exists")
do{
let fetchRequest = NSFetchRequest<Styles>(entityName:"Styles")
let predicate = NSPredicate(format: "id == %d",item["id"] as! Int32)
fetchRequest.predicate = predicate
let req = try CoreDataStack.sharedManager.mainObjectContext.fetch(fetchRequest)
let object = req[0] as NSManagedObject
object.setValue(item["style"] as! String, forKey: "style")
object.setValue(item["image"] as! String, forKey: "image")
CoreDataStack.sharedManager.saveMainContext()
}catch{
print("there was an error")
}
completionHandler()
}
}
break;
default:
break
}
}
break
case let .failure(error):
print(error as NSError)
break
}
}
}
}
protocol CoreDataWorkerProtocol {
associatedtype EntityType
}
enum VoidResult {
case success
case failure(NSError)
}
Response json is called on background thread and you are trying to use viewContext in background thread.
You should use perform block if you want to use viewContext in background.
For example like this
AF.request(:).responseJSON(queue:queue){ response in
let mainContext = CoreDataStack.sharedManager.mainObjectContext
mainContext.perform {
guard let entity = NSEntityDescription.entity(forEntityName: table, in: mainContext) else {
fatalError("Could not find entity descriptions!")
}
}
}
coreData returns empty data when there should not be any, even if you uninstall the application and reinstall it and make a request to Сore Data, the context.fetch returns the data
get all Data in Сore Data
func getMyLoadBook(){
words.removeAll()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest:NSFetchRequest<Favorite> = Favorite.fetchRequest()
fetchRequest.returnsObjectsAsFaults = false
do {
let result = try! context.fetch(fetchRequest)
print(result)
if result.isEmpty {
emptyBookMark()
return
} else {
tableView.isHidden = false
}
for data in result as [NSManagedObject] {
if let _ = data.value(forKey: "word"){
let initData = Words(word: (data.value(forKey: "word") as? [String]) ?? [""], wordDesc: (data.value(forKey: "wordDesc") as? [String]) ?? nil, translation: (data.value(forKey: "translation") as? [String]) ?? [""], translDesc: (data.value(forKey: "translDesc") as? [String]) ?? nil)
words.append(initData)
}
}
}
tableView.reloadData()
}
I have these functions, but they are not called when I get data from coreData
// creates a path and checks for the presence of an element
static func coreDataResult(data: [[String?]?]?, completion: #escaping (NSFetchRequest<NSFetchRequestResult>, Favorite?, NSManagedObjectContext) -> ()){
guard let w = data?.first, let word = w, let t = data?.last, let transl = t else { return }
DispatchQueue.main.async {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
guard let entity = NSEntityDescription.entity(forEntityName: "Favorite", in: context) else { return }
guard let taskObject = NSManagedObject(entity: entity, insertInto: context) as? Favorite else { return }
let predicate = NSPredicate(format: "word == %#", word)
let predicate2 = NSPredicate(format: "translation == %#", transl)
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Favorite")
let andPredicate = NSCompoundPredicate(type: .and, subpredicates: [predicate, predicate2])
fetchRequest.predicate = andPredicate
completion(fetchRequest, taskObject, context)
}
}
// remove data from Сore Data
static func deleteFromCoreData(data: [[String?]?]?){
coreDataResult(data: data, completion: { (result, taskObject, context) in
do {
let fetchedEntities = try context.fetch(result) as! [Favorite]
if let entityToDelete = fetchedEntities.first {
context.delete(entityToDelete)
}
do {
try context.save()
if let data = getDataFromContext(result:fetchedEntities){
Analytics.logEvent("RemovedFavorite", parameters: ["word": data.0, "translation": data.1])
YMMYandexMetrica.reportEvent("RemovedFavorite", parameters: ["word": data.0, "translation": data.1], onFailure: nil)
}
} catch {
print(error)
}
} catch { print(error) }
})
}
// add data to Сore Data
static func saveWithModelToCoreData(_ words: Words){
DispatchQueue.main.async {
coreDataResult(data: [words.word, words.translation], completion: { (result, taskObject, context) in
do {
let fetchedEntities = try context.fetch(result) as! [Favorite]
if let _ = fetchedEntities.first?.word {
print("the element already have in coreData")
} else {
taskObject?.setValue(words.word, forKey: "word")
taskObject?.setValue(words.translation, forKey: "translation")
taskObject?.setValue(words.descript, forKey: "wordDesc")
taskObject?.setValue(words.translDesc, forKey: "translDesc")
do {
try context.save()
} catch {
print(error)
}
}
} catch {
print(error)
}
})
}
}
that's what result returns
[<Favorite: 0x283478500> (entity: Favorite; id: 0x281306ee0 <x-coredata:///Favorite/t722DD7F9-8DD7-4AC4-AA20-02324AB1B08713> ; data: {
translDesc = nil;
translation = nil;
word = nil;
wordDesc = nil;
})
It seems that you are you using a simple core-data setup, where all read and write are done on the main thread to the viewContext. This setup is fine for simple application where you don't expect to do a bulk import, or have a huge amount of entities. It should simplify a lot of multithread issues so I am a little confused why you have such a complex setup with callbacks and DispatchQueue.main.async when everything should just simply run on the main thread. (Perhaps you are planing for a future with a more complex setup?).
In any event, one of the consequences of this is that any changes to the viewContext will appear in your app for the lifetime of the app, even if you don't call save. This is because there is a single context - so even it is not saved, it has still been changed.
In the method coreDataResult you create an empty object, and then in saveWithModelToCoreData it is either set with values and the context saved or it is found to already exist and no further action is taken. If coreDataResult returned on a background context that would be fine. The empty object would disappear when the background context. The problem is that you are writing to the viewContext so the context does not go away, and the object sticks around.
If the application would quit right then, you wouldn't see it in the next launch. But if save is called any time after, then the empty object will also be saved.
I would suggest not creating objects unless you already know that you want them. I would refactor so that there is a single function that checks for duplicate, and then creates and set or does nothing. As it is I don't see the value of the two different methods.
Unfortunately the new Core Data semantics make me crazy. My previous question had a clean code that didn't work because of incorrect auto generation of header files. Now I continue my work with deleting objects.
My code seems to be very simple:
func deleteProfile(withID: Int) {
let fetchRequest: NSFetchRequest<Profile> = Profile.fetchRequest()
fetchRequest.predicate = Predicate.init(format: "profileID==\(withID)")
let object = try! context.fetch(fetchRequest)
context.delete(object)
}
I did a "hard" debug with print(object) instead of context.delete(object) and it showed me the right object.
So I need just to delete it.
P.S. there is no deleteObject. Now NSManagedContext has only public func delete(_ sender: AnyObject?)
The result of a fetch is an array of managed objects, in your case
[Event], so you can enumerate the array and delete all matching objects.
Example (using try? instead of try! to avoid a crash in the case
of a fetch error):
if let result = try? context.fetch(fetchRequest) {
for object in result {
context.delete(object)
}
}
do {
try context.save()
} catch {
//Handle error
}
If no matching objects exist then the fetch succeeds, but the resulting
array is empty.
Note: In your code, object has the type [Event] and therefore in
context.delete(object)
the compiler creates a call to the
public func delete(_ sender: AnyObject?)
method of NSObject instead of the expected
public func delete(_ object: NSManagedObject)
method of NSManagedObjectContext. That is why your code compiles
but fails at runtime.
The trick here, it is save context after deleting your objects.
let fetchRequest: NSFetchRequest<Profile> = Profile.fetchRequest()
fetchRequest.predicate = Predicate.init(format: "profileID==\(withID)")
let objects = try! context.fetch(fetchRequest)
for obj in objects {
context.delete(obj)
}
do {
try context.save() // <- remember to put this :)
} catch {
// Do something... fatalerror
}
I hope this can help someone.
func deleteRecords() {
let delegate = UIApplication.shared.delegate as! AppDelegate
let context = delegate.persistentContainer.viewContext
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "nameofentity")
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try context.execute(deleteRequest)
try context.save()
} catch {
print ("There was an error")
}
}
Delete core data objects swift 3
// MARK: Delete Data Records
func deleteRecords() -> Void {
let moc = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
let result = try? moc.fetch(fetchRequest)
let resultData = result as! [Person]
for object in resultData {
moc.delete(object)
}
do {
try moc.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
// MARK: Get Context
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
Swift 4.1, 4.2 and 5.0
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let requestDel = NSFetchRequest<NSFetchRequestResult>(entityName: "Users")
requestDel.returnsObjectsAsFaults = false
// If you want to delete data on basis of some condition then you can use NSPredicate
// let predicateDel = NSPredicate(format: "age > %d", argumentArray: [10])
// requestDel.predicate = predicateDel
do {
let arrUsrObj = try context.fetch(requestDel)
for usrObj in arrUsrObj as! [NSManagedObject] { // Fetching Object
context.delete(usrObj) // Deleting Object
}
} catch {
print("Failed")
}
// Saving the Delete operation
do {
try context.save()
} catch {
print("Failed saving")
}
Swift 4 without using string for Entity
let fetchRequest: NSFetchRequest<Profile> = Profile.fetchRequest()
fetchRequest.predicate = Predicate.init(format: "profileID==\(withID)")
do {
let objects = try context.fetch(fetchRequest)
for object in objects {
context.delete(object)
}
try context.save()
} catch _ {
// error handling
}
Delete Core Data Object with query in Swift 5, 4.2
let fetchRequest = NSFetchRequest<Your_Model>(entityName: "Your_Entity_Name")
fetchRequest.predicate = NSPredicate(format: "any your_key == %d", your_value)
hope this will help to someone
Swift 5
Common function for Deleting core data objects for any anity swift 5
func deleteEntityObjectByKeyValue<T>(className: T.Type, key: String, value: Any) -> Bool {
let context = CoreDataStack.sharedStack.mainContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: className.self))
//Search based on data type.I used for Int and String
if let sValue = value as? String {
let predicate = NSPredicate(format: "\(key) == %#", sValue)
fetchRequest.predicate = predicate
} else if let iValue = value as? Int64 {
let predicate = NSPredicate(format: "\(key) == %d", iValue)
fetchRequest.predicate = predicate
}
do {
let result = try context.fetch(fetchRequest)
if result.count != 0 {
if let managedObject = result[0] as? NSManagedObject {
context.delete(managedObject)
do {
try context.save()
return true
}
catch let error {
print(error.localizedDescription)
}
}
}
return false
} catch let error {
print(error.localizedDescription)
}
return false
}
How To Use it:
let isSaved = CoreDataOperations.shared.deleteEntityObjectByKeyValue(className: EmpolyeeData.self, key: "employeeId", value:1234)
If isSaved {
print("Deleted obj Successfully")
}
Swift 4,5
It is useful to delete particular record or all records from entity.
1.Create a NSPersistentContainer and NSManagedObjectContext using below code.
class CoreDataStack: NSObject {
static var sharedStack = CoreDataStack()
private override init() {}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Employee")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let url = storeDescription.url {
print("SQLITE STORE LOCATION: \(url.absoluteString)")
}
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private lazy var applicationDocumentsDirectory: URL = {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return urls[urls.count-1]
}()
private lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = Bundle.main.url(forResource: "____", withExtension: "____")!
return NSManagedObjectModel(contentsOf: modelURL)!
}()
public lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.appendingPathComponent("Employee" + ".sqlite")
var failureReason = "There was an error creating or loading the application's saved data."
do {
let options = [ NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true ]
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options)
} catch {
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
dict[NSUnderlyingErrorKey] = error as NSError
let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
}
return coordinator
}()
lazy var mainContext: NSManagedObjectContext = {
var managedObjectContext: NSManagedObjectContext?
if #available(iOS 10.0, *){
managedObjectContext = self.persistentContainer.viewContext
managedObjectContext?.mergePolicy = NSMergePolicy.init(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
}
else{
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
managedObjectContext?.persistentStoreCoordinator = coordinator
managedObjectContext?.mergePolicy = NSMergePolicy.init(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
}
return managedObjectContext!
}()
}
2.Common function for Deleting core data all objects for any Entity swift 5
func deleteEntityData(entity : String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
do {
try CoreDataStack.sharedStack.mainContext.execute(deleteRequest)
CoreDataStack.sharedStack.saveMainContext()
} catch {
print ("There was an error")
}
}
3.How to use above code.
self.deleteEntityData(entity : "Employee")
4.If you want to delete Particular object from Entity
func deleteEntityObjectByKeyValue<T>(entityName: T.Type, key: String, value: Any) -> Bool {
let context = CoreDataStack.sharedStack.mainContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: entityName.self))
if let sValue = value as? String {
let predicate = NSPredicate(format: "\(key) == %#", sValue)
fetchRequest.predicate = predicate
} else if let iValue = value as? Int64 {
let predicate = NSPredicate(format: "\(key) == %d", iValue)
fetchRequest.predicate = predicate
}
do {
let result = try context.fetch(fetchRequest)
if result.count != 0 {
if let managedObject = result[0] as? NSManagedObject {
context.delete(managedObject)
do {
CoreDataStack.sharedStack.saveMainContext()
return true
}
catch let error {
print(error.localizedDescription)
}
}
}
return false
} catch let error {
print(error.localizedDescription)
}
return false
}
Delete the object from core data
let entity = NSEntityDescription.entity(forEntityName: "Students", in: managedContext)
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = entity
if let result = try? managedContext.fetch(request) {
for object in result {
managedContext.delete(object as! NSManagedObject)
}
txtName.text = ""
txtPhone.text = ""
txt_Address.text = ""
labelStatus.text = "Deleted"
}
I get objects array from core data:
lazy var managedObjectContext : NSManagedObjectContext? = {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if let managedObjectContext = appDelegate.managedObjectContext {
return managedObjectContext
}
else {
return nil}
}()
var tests=[Test]()
func fetchLog() {
let fetchRequest = NSFetchRequest(entityName: "Test")
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [Test] {
tests = fetchResults
}
And then tray to update it:
managedObjectContext?.updatedObjects(tests[atRow] as NSManagedObject)
But get error: '(NSManagedObject) -> $T5' is not identical to 'NSSet'
Whats wrong?
NSManagedObject is registered with NSManagedObjectContext(MOC). To update it, call MOC's save().
like this:
let aTest = test[atRow] as Test
// update through properties like aTest.id = 2
...
// and save
var error: NSError?
if managedObjectContext!.save(&error) {
println("saved successfully")
} else {
println("failed to save")
if let saveError = error {
println("error=\(saveError.localizedDescription)")
}
}