how to save values in core data in swift - swift

I have to apply checkmarks on the contacts received by using address book. When I'm selecting contacts then checkmarks appears but after scrolling it disappears. Secondly, I have to save these selected contacts into core data. Just have a look at that and tell me what I'm doing wrong.What Wrong i'am doing with Core Data.

class LogItem: NSManagedObject {
#NSManaged var section: String
#NSManaged var keys: String
}
Now declare the object of this class like this:
let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as! LogItem
newItem.section = "section Title"
newItem.keys = "keys text"
}
You can fetch the data as follows:
// request using the LogItem entity
let fetchRequest = NSFetchRequest(entityName: "LogItem")
// Execute the fetch request, and cast the results to an array of LogItem objects
if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
println(fetchResults[0].section) // prints "section title"
println(fetchResults[0].key) // prints key text
}
Please Make sure that core data is able to save only properties.
According to apple documentation, “The Core Data framework provides
generalized and automated solutions to common tasks associated with
object life-cycle and object graph management, including persistence.”

First, you add CoreData, then go to AppDelegate and you will see this code:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model_data")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
In: let container . . . you see (name: "Model_data")
This name needs to be same as your model file name.
Then go to your View Controller and add:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newItem = Item(context: self.context)
Item is an entity from your model object.
add newItem to an array like:
self.itemArray.append(newItem)
Then, add next logic:
fileprivate func saveContext() {
do {
try context.save()
// if it is table view, you need to reload here. tableView.reloadData()
} catch {
print("Failed to with error: \(error)")
}
}
if loading is problem add this logic:
fileprivate func loadContext() {
let request: NSFetchRequest<Item> = Item.fetchRequest()
do {
itemModel = try context.fetch(request)
} catch {
print("Error in request: \(error)")
}
}
Save and load.

Related

XCTest only: No NSEntityDescriptions in any model claim the NSManagedObject subclass

Let me outline the relevant code:
I have a DataManager class as follows:
enum DataManagerType {
case normal, preview, testing
}
class DataManager: NSObject, ObservableObject {
static let shared = DataManager(type: .normal)
static let preview = DataManager(type: .preview)
static let testing = DataManager(type: .testing)
#Published var todos = [Todo]()
fileprivate var managedObjectContext: NSManagedObjectContext
private let todosFRC: NSFetchedResultsController<TodoMO>
private init(type: DataManagerType) {
switch type {
case .normal:
let persistentStore = PersistentStore()
self.managedObjectContext = persistentStore.context
case .preview:
let persistentStore = PersistentStore(inMemory: true)
self.managedObjectContext = persistentStore.context
for i in 0..<10 {
let newTodo = TodoMO(context: managedObjectContext)
newTodo.title = "Todo \(i)"
newTodo.isComplete = false
newTodo.date = Date()
newTodo.id = UUID()
}
try? self.managedObjectContext.save()
case .testing:
let persistentStore = PersistentStore(inMemory: true)
self.managedObjectContext = persistentStore.context
}
let todoFR: NSFetchRequest<TodoMO> = TodoMO.fetchRequest()
todoFR.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]
todosFRC = NSFetchedResultsController(fetchRequest: todoFR,
managedObjectContext: managedObjectContext,
sectionNameKeyPath: nil,
cacheName: nil)
super.init()
// Initial fetch to populate todos array
todosFRC.delegate = self
try? todosFRC.performFetch()
if let newTodos = todosFRC.fetchedObjects {
self.todos = newTodos.map({todo(from: $0)})
}
}
func saveData() {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch let error as NSError {
NSLog("Unresolved error saving context: \(error), \(error.userInfo)")
}
}
}
private func fetchFirst<T: NSManagedObject>(_ objectType: T.Type, predicate: NSPredicate?) -> Result<T?, Error> {
let request = objectType.fetchRequest()
request.predicate = predicate
request.fetchLimit = 1
do {
let result = try managedObjectContext.fetch(request) as? [T]
return .success(result?.first)
} catch {
return .failure(error)
}
}
}
My persistence store is as such:
struct PersistentStore {
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreDataModel")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
var context: NSManagedObjectContext { container.viewContext }
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
NSLog("Unresolved error saving context: \(error), \(error.userInfo)")
}
}
}
}
I get an error when calling the following in an XCTest:
let predicate = NSPredicate(format: "id = %#", todo.id as CVarArg) //todo.id is just some UUID() //irrelevant here
let result = fetchFirst(TodoMO.self, predicate: predicate)
This is the error I get:
2022-07-09 21:36:17.425709-0400 CoreDataExample[73965:7495035] [error]
error: No NSEntityDescriptions in any model claim the NSManagedObject
subclass 'CoreDataExampleTests.TodoMO' so +entity is confused. Have
you loaded your NSManagedObjectModel yet ? CoreData: error:
No
NSEntityDescriptions in any model claim the NSManagedObject subclass
'CoreDataExampleTests.TodoMO' so +entity is confused. Have you loaded
your NSManagedObjectModel yet ?
2022-07-09 21:36:17.425780-0400 CoreDataExample[73965:7495035] [error] error:
+[CoreDataExampleTests.TodoMO entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[CoreDataExampleTests.TodoMO entity] Failed to find a unique
match for an NSEntityDescription to a managed object subclass
/Users/santiagogarciasantos/Documents/Xcode
Projects/CoreDataExample/CoreDataExample/DataManager/DataManager.swift:87:
error: -[CoreDataExampleTests.CoreDataExampleTests test_Add_Todo] :
executeFetchRequest:error: A fetch request must have an entity.
(NSInvalidArgumentException)
Following other solutions on here, I've checked the target, made sure my Entity is in "Current Product Module" but it still won't work.
Important: This only occurs when I'm in using XCTest (using my DataManager.testing), not in Previews or in simulator
Here's a link to a demo project I've made which recreates the issue.
Thanks for your help!
I made a couple of changes to your code to make the tests work:
you should not add your app classes to the test target. They are imported automatically by selecting a "Host Application" in you test target and made accessible by the directive #testable import CoreDataExample.
when the test execute, an instance of CoreDataExampleApp is created. This in turn instantiates var dataManager = DataManager.shared.
Then the tests execute where you instantiate dataManager = DataManager.testing.
Two instances of PersistentStore are created which have their own version of the core data stack including the managedObjectModel.
Those models are fighting over your NSManagedObject subclasses which results in objectType.fetchRequest() having no entity.
To fix your issue, go through all files in your app target and make sure in FileInspector>TargetMembership only CoreDataExample is checked. Then in CoreDataExampleTests change line 17 to dataManager = DataManager.shared.
Your tests will run now.
If you want to keep your different DataManager flavours, you have to make sure that only one instance of PersistentStore is ever created. One simple way would be to make it static:
class DataManager: NSObject, ObservableObject {
static let persistentStore = PersistentStore()
// [...]
private init(type: DataManagerType) {
switch type {
case .normal:
self.managedObjectContext = DataManager.persistentStore.context
// etc.
Edit: Alternative Solution
The issue comes from using the TestTarget>General>'Host Application' feature in the first place, which Xcode sets now for new projects. This is meant for Application Tests where you need an app instance.
If you instead want to perform Logic Tests you should opt out of this feature. You then don't get the app instance with its side effects. In your case this is probably what you want. The tests will also run faster because the app does not need to be loaded.
But you then need to add all your files manually to your test target.
To use the alternative approach:
deselect "Host Application"
Manually add all relevant files
You have to help PersistentStore finding the right model file by loading it from an explicit URL. Change this in your test class:
override func setUp() {
super.setUp()
let testBundle = Bundle(for: type(of: self))
let modelUrl = testBundle.url(forResource: "CoreDataModel", withExtension: "momd")
dataManager = DataManager(type: .testing, modelUrl: modelUrl)
}
Change your DataManager and PersistentStore accordingly to handle the optional URL:
// DataManager change not shown (just pass through)
struct PersistentStore {
init(inMemory: Bool = false, modelUrl: URL? = nil) {
if let url = modelUrl {
let mom = NSManagedObjectModel(contentsOf: url)! // todo: handle !
container = NSPersistentContainer(name: "CoreDataModel", managedObjectModel: mom) // todo: maybe get name from URL
} else {
container = NSPersistentContainer(name: "CoreDataModel")
}
I recommend this second approach, I consider it much cleaner.
If you also need Application Tests, add an additional target.

Multithreaded Core Data object has empty properties on main thread after saving on background thread

As shortly described in the title, after I save an object on background thread, its properties are empty on the main thread, such as strings being "", numbers 0 etc.
Here's some code!
User class:
#objc(User)
class User: NSManagedObject {
#NSManaged var id: Int32
#NSManaged var email: String
}
UserRepository where the actual save is happening:
func saveUser(fromJSON json: Any, onSuccess: ((User) -> Void)?, onFailure: ((Error) -> Void)?) {
dataManager.persistentContainer.performBackgroundTask { context in
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
let user = self.userFactory.user(fromJSON: json, inContext: context)
// print(user.id) and print(user.email) output correct data
do {
try context.save()
DispatchQueue.main.async {
// print(user.id) and print(user.email) show 0 and ""
onSuccess?(user)
}
} catch let error {
DispatchQueue.main.async {
onFailure?(error)
}
}
}
}
Where dataManager is configured like this:
class CoreDataManager {
private let modelName: String
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
})
return container
}()
init(modelName: String) {
self.modelName = modelName
}
}
I'm guessing userFactory is not that important, but for the sake of completeness:
class UserFactory {
func user(fromJSON json: Any, inContext context: NSManagedObjectContext) -> User {
guard
let jsonDictionary = json as? [String: Any?],
let userDictionary = jsonDictionary["user"] as? [String: Any?],
let id = userDictionary["id"] as? Int32,
let email = userDictionary["email"] as? String
else {
fatalError("Could not parse User object.")
}
let user = User(context: context)
user.id = id
user.email = email
return user
}
}
Please see the comments in the snippets above in regards to print statements which point out the places where user properties are fine and where they show 0 and "".
I'm aware I can't pass NSManagedObjects between threads, but even if I try fetching the user object in the DispatchQueue.main.async block using its objectID (NSManagedObjectID), user properties are still empty.
I guess I'm doing something wrong, but can't put a finger on it. Any advice would be greatly appreciated.
Thanks very much in advance!
The problem, I think, is that Core Data is not thread safe. I know you know this - that, after all, is why you created a special background context - but it has very strict implications. You are saying:
dataManager.persistentContainer.performBackgroundTask { context in
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
let user = self.userFactory.user(fromJSON: json, inContext: context)
do {
try context.save()
DispatchQueue.main.async {
print(user) // <--
You can't do that. You're still talking about the user you saved into the background context, but you've switched threads. No. You cannot talk about this user from two different threads.
Once you are on the main thread, the only way you may talk to Core Data is through the main context. You need to fetch the user out, by its id, from the main context. Now you can look to see how it is populated.

CoreData in background thread in Swift 2.0. Clean way?

I have been struggling with this problem for exactly 8 days now. So I believe it's time to ask for help for the Gurus over here.
Ok, so I am trying to implement an app following Uncle Bob's Clean Architecture, so I have the ViewControllers, Models, Interactors, Repositories and Presenters all setup.
My AppDelegate does not have any traces of CoreData in it, nothing. All that is done in the MyAppCoreData class.
The repository is injected in the AppDelegate and the Interactor access the injected object to access not only CoreData stuff, but also Parse and another private API. The Parse and private API are working great.
The CoreData repository also "works". It does not throw any exception. But no data is inserted in CoreData and when I fetch, it's empty.
I believe I am having some problem with the Persistent Store Coordinator + Main Context + Private Context... but have not yet managed to find out what it is.
So let's go to the code:
AppDelegate
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let container = Container() { c in
c.register(UserDataStore.self) { _ in ParseRepository() }
c.register(BabyNamesDataStore.self) { _ in BabyNameRepository.sharedInstance }
c.register(GenericRepository.self) { r in
GenericRepository(_userDataStore: r.resolve(UserDataStore.self)!, _babyNameDataStore: r.resolve(BabyNamesDataStore.self)!)
}
}
...
Then, my interactors have the following init() function:
class MainInteractor: MainInteractorInput
{
var output: MainInteractorOutput!
var worker: GenericRepository?
init() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
worker = appDelegate.container.resolve(GenericRepository.self)
}
Then, finally, my CoreDataStore is a singleton (before it wasn't and I could see that it was being instantiated twice for some reason by Swinject):
class BabyNamesCoreDataStore : BabyNamesDataStore{
var mainManagedObjectContext: NSManagedObjectContext
var privateManagedObjectContext: NSManagedObjectContext
static let sharedInstance = BabyNamesCoreDataStore()
init()
{
QL1("BabyNamesCoreDataStore init")
// This resource is the same name as your xcdatamodeld contained in your project.
guard let modelURL = NSBundle.mainBundle().URLForResource("BabyNameMe", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOfURL: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainManagedObjectContext.persistentStoreCoordinator = psc
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
let docURL = urls[urls.endIndex-1]
/* The directory the application uses to store the Core Data store file.
This code uses a file named "DataModel.sqlite" in the application's documents directory.
*/
let storeURL = docURL.URLByAppendingPathComponent("BabyNameMe.sqlite")
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
fatalError("Error migrating store: \(error)")
}
//let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
privateManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedObjectContext.parentContext = mainManagedObjectContext
}
deinit
{
do {
try self.mainManagedObjectContext.save()
} catch {
fatalError("Error deinitializing main managed object context")
}
}
func fetchBabyNames(completionHandler: (babyNames: [BabyNames], error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let fetchRequest = NSFetchRequest(entityName: "ManagedBabyNames")
let predicate = NSPredicate(format: "liked == nil")
fetchRequest.predicate = predicate
let results = try self.privateManagedObjectContext.executeFetchRequest(fetchRequest) as! [ManagedBabyNames]
let result = results.map { $0.toBabyName() }
completionHandler(babyNames: result, error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler(babyNames: [], error: StoreError.CannotFetch("Cannot fetch baby names"))
}
}
}
func insertBabyNames(babyNames: BabyNames, completionHandler: (error: StoreError?) -> Void) {
privateManagedObjectContext.performBlock {
do {
let managedBabyNames = NSEntityDescription.insertNewObjectForEntityForName("ManagedBabyNames", inManagedObjectContext: self.privateManagedObjectContext) as! ManagedBabyNames
managedBabyNames.fromBabyName(babyNames)
try self.privateManagedObjectContext.save()
completionHandler(error: nil)
} catch let error as NSError {
QL4("Could not save \(error), \(error.userInfo)")
completionHandler (error: StoreError.CannotCreate("Cannot create baby names with id \(babyNames.id)"))
}
}
}
...
And this is pretty much it. I get no exceptions but it is simply not working.
Could anyone please help this desperate man ? :)
Thanks!
UPDATE
I am following Raymund's Clean Store idea to implement my own, found here: https://github.com/Clean-Swift/CleanStore
I found out that the function that receives the response from the Presenter, inside the View Controller is NOT in the main thread. So I had to add this:
func displaySomething(viewModel: LoginViewModel) {
if viewModel.loginStatus {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.prepareSlideOutVC()
//When user is not logged in, the SlideOut Menu is not loaded. So when he logs in, we must load it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_async(dispatch_get_main_queue()) {
self.router.navigateToMainScene()
}
}
}
}
adding the dispatch_async sorted out the problem in a few cases, but not all of them... still trying to figure out.
So, basically, after I login and receive the response from the Presenter, I activate the segue to go to MainVC but that was in a different thread.
Still trying to work this out.

delete core data entry doesn't work

I try to delete an core data entry using Swift. I also use the fetched results controller for loading the entries. This is my code:
let context = self.fetchedResultsController.managedObjectContext
let fetchRequest = NSFetchRequest(entityName:"Person")
fetchRequest.predicate = NSPredicate(format: "name = '\(item)'")
var error : NSError?
if let results = context.executeFetchRequest(fetchRequest, error:&error),
let managedObject = results.first as? NSManagedObject {
context.deleteObject(managedObject)
}
I don't know why but if this code runs the entry is deleted out of the table but if i restart the app the table includes the task which i've deleted.
This only deletes the object from the managed object context (which is the scratchpad for making changes). To persist anything done in a managed object context to the underlying database you need to save it first:
if let results = context.executeFetchRequest(fetchRequest, error:&error),
let managedObject = results.first as? NSManagedObject {
context.deleteObject(managedObject)
}
let saveError: NSError?
context.save(&saveError)
You need to save.
Swift 1.2
context.save(nil)
Swift 2
do { try context.save() } catch {}

Update all value in one attribute Core Data

I know how to fetch all value from one attribute in Core Data using an array. I just need to press a button and -1 all the value and save it back to the Core Data.
How can I update all the value once in swift?
Thanks.
For swift 3
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Params")
do{
if let fetchResults = try managedContext.fetch(request) as? [NSManagedObject] {
if fetchResults.count != 0 {
for index in 0...fetchResults.count-1 {
let managedObject = fetchResults[index]
managedObject.setValue("-1", forKey: "value")
}
try managedContext.save()
}
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
Try following example it might be helpful.Replace you entity name.
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
var context: NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Params")
var params = NSEntityDescription.insertNewObjectForEntityForName("Params", inManagedObjectContext: context) as! NSManagedObject
if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [NSManagedObject] {
if fetchResults.count != 0{
for (var v = 0 ; v < fetchResults.count ; v++) {
var managedObject = fetchResults[v]
println(fetchResults)
managedObject.setValue("-1", forKey: "value")
context.save(nil)
}
}
}
While the question is quiet old, I am writing it for those who wish to do this task in a more optimized manner. According to the documentation,
Batch updates run faster than processing the Core Data entities yourself in code because they operate in the persistent store itself, at the SQL level.
Hence using NSBatchUpdateRequest is the best way to achieve the result. Here's a swift code that could do the job in the given scenario with using an extension to help simplify the code while also taking into account the fact that changes made in batch update are not reflected in the objects currently in memory.
extension NSManagedObjectContext {
/// Executes the given `NSBatchUpdateRequest` and directly merges the changes to bring the given managed object context up to date.
///
/// - Parameter batchUpdateRequest: The `NSBatchUpdateRequest` to execute.
/// - Throws: An error if anything went wrong executing the batch deletion.
public func executeAndMergeChanges(using batchUpdateRequest: NSBatchUpdateRequest) throws {
batchUpdateRequest.resultType = .updatedObjectIDsResultType
let result = try execute(batchUpdateRequest) as? NSBatchUpdateResult
let changes: [AnyHashable: Any] = [NSUpdatedObjectsKey: result?.result as? [NSManagedObjectID] ?? []]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self])
}
}
class MyCoreDataClass {
...
func updateAllParams() {
let request = NSBatchUpdateRequest(entityName: "Params")
request.propertiesToUpdate = ["value" : NSExpression(forConstantValue: -1)]
do {
try managedObjectContext.executeAndMergeChanges(using: request)
} catch {
print(error)
}
}
}
P.S: You need to be aware that validation rules enforced in data model are not considered while executing a batch update. According to the documentation,
When you use batch updates any validation rules that are a part of the data model are not enforced when the batch update is executed. Therefore, ensure that any changes caused by the batch update will continue to pass the validation rules.