I have an application using Core Data as its database, I'm trying to write test cases for my DB queries which I have written inside my NSManagedObject subclass and they will return true if the insertion works successfully. I'm trying to insert an object into my entity like this:
func testDBInser() {
let appDelegate: AppDelegate = AppDelegate()
let managedObjectContext = appDelegate.persistentContainer.viewContext
let myObject: MyManagedObject = MyManagedObject(context: managedObjectContext)
myObject.id = 10
myObject.name = "testObj"
XCTAssertTrue(UserProfileModel().insertObjectToUserProfile(myObject))
}
But I'm getting this error for my test:
An NSManagedObject may only be in (or observed by) a single NSManagedObjectContext
Life is a lot easier with CoreStore:
func testInsert {
// 1. Arrange
let dataStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "ModelName")
do {
try dataStack.addStorageAndWait()
} catch let error {
XCTFail("Cannot set up database storage: \(error)")
}
return dataStack
}()
// 2. Action
do {
try dataStack.perform(synchronous: { transaction in
let object = transaction.create(Into<TestObject>())
object.name = "Test"
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
// 3. Assert
do {
try dataStack.perform(synchronous: { transaction in
guard transaction.fetchOne(From<TestObject>(), Where("name", isEqualTo: "Test")) != nil else {
XCTFail("Cannot get database object")
}
})
} catch let error {
XCTFail("Cannot perform database transaction: \(error)")
}
}
Related
I want my swift code to print out the strings attributes. Right now when calling the function I am getting a runtime error at context. I just want to print out all of each string entry. I have added the function in question below.
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let appDelegate = UIApplication.shared.delegate as! AppDelegate //Singlton instance
var context:NSManagedObjectContext!
#objc func pressRight(){
let fetchRequest = NSFetchRequest<Place>(entityName: "Name")
do {
let result = try context.fetch(fetchRequest)
let nameArray = result.map{$0.name}
print(nameArray)
} catch {
print("Could not fetch \(error) ")
}
}
pic
select manual in code gen
then create custom class of place add to your project
You are using the wrong entity name "Name" instead of "Place"
import Foundation
import CoreData
class CoreDataManager {
static let shared = CoreDataManager()
private init() {}
lazy var coreDataStack = CoreDataStack(modelName: "Place")
func allNames() -> [String]? {
let request: NSFetchRequest<Place> = Place.fetchRequest()
do {
// Peform Fetch Request
let places = try coreDataStack.managedContext.fetch(request)
return places.map({$0.name})
} catch {
print("Unable to Fetch Workouts, (\(error))")
}
return nil
}
func allPlaces() -> [Place]? {
let request: NSFetchRequest<Place> = Place.fetchRequest()
do {
// Peform Fetch Request
let places = try coreDataStack.managedContext.fetch(request)
return places
} catch {
print("Unable to Fetch Workouts, (\(error))")
}
return nil
}
}
if you still getting error then before this initialize your context
managedObjectContext/context you force unwrapping it
add this stack class
import Foundation
import CoreData
class CoreDataStack {
private let modelName: String
lazy var managedContext: NSManagedObjectContext = {
return self.storeContainer.viewContext
}()
init(modelName: String) {
self.modelName = modelName
}
private lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
print("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
func saveContext() {
guard managedContext.hasChanges else {return}
do{
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func updateContext() {
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
func clearChange() {
managedContext.rollback()
}
}
then how to use it
in your view controller viewDidLoad() function or any other button tap action you can get your place names like this
override func viewDidLoad() {
super.viewDidLoad()
// here you get all names
let names = CoreDataManager.shared.allNames()
print(names)
let places = CoreDataManager.shared.allPlaces()
print(places)
let namesAgain = places.map({$0.name})
print(namesAgain)
}
I am trying to save data in CoreData. When the app is running everything is ok. I receive info that data is saved and receive messages that it is fetching correct. But when I close the app data just disappeared somewhere.
When I created the project I do not check Core Data, so I added xcdatamodel, import CoreData everywhere, updated AppDelegate with the correct NSPersistentContainer name (the name is name of my xcdatamodel) also in Project-General-Frameworks added CoreData.framework.
Here is part of saving, fetching, and deleting data. The file is separate from VC. I do not receive any type of errors.
In my VC I just call savedata(), to save the data. It works before the app is closed.
import UIKit
import CoreData
var cgsTeams = [TeamsCoreData]()
func savedata() {
saveTeams { (complete) in
if complete {print("TeamsSaved")}
}
}
func saveTeams(completion: (_ finished: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else {return}
let privateManagedContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateManagedContext.parent = managedContext
for team in teams {
let teamsCoreData = TeamsCoreData(context: privateManagedContext)
teamsCoreData.team = team.name
teamsCoreData.score = Int32(team.score)
teamsCoreData.number = Int32(team.number)
do{
try privateManagedContext.save()
debugPrint("Succesfully saved teamsCoreData")
completion(true)
} catch{
debugPrint("Could not save - \(error)")
completion(false)
}
}
}
func fetchTeams(completion: (_ complete: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
do {
cgsTeams = try managedContext.fetch(fetchRequest)
print("cgsGameRules fetched")
teams = [team]()
for cgsTeam in cgsTeams {
print("team - \(cgsTeam.team!) added")
teams.append(team(name:cgsTeam.team!, number: Int(cgsTeam.number), score: Int(cgsTeam.score)))
}
if cgsTeams.count > 1{completion(true)} else {completion (false); print("No teams")}
} catch {
debugPrint("Could not fetch: \(error.localizedDescription)")
completion(false)
}
}
func deleteTeams(){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchRequest = NSFetchRequest<TeamsCoreData>(entityName: "TeamsCoreData")
let objects = try! managedContext.fetch(fetchRequest)
for obj in objects {
managedContext.delete(obj)
}
do {
try managedContext.save()
} catch {
print("error on delete Team")
}
}
When you save changes in Core Data, the context saves only to its parent context. If it doesn't have a parent context, it saves changes to the persistent store file. You're saving changes on privateManagedObjectContext, which is a child context of viewContext. But you're never saving changes on viewContext. So your child context is telling the parent context about the changes, but the parent never saves those changes anywhere.
You need to either (a) save changes on viewContext, or (b) make privateManagedObjectContext its own stand-alone context, not a child context.
Application crashes when saving in Core data
do {
try context.save() <----------- CRASH ---------
} catch {
print("Failed saving")
}
I save data in Core data in background using this code
DispatchQueue.global(qos: .background).async {
// Fetches from database
DispatchQueue.main.async {
if fetchedActivitiesFromDB {
// Updates the view
} else {
// gets from the server
// Save the result in Core Data <------------
let coreData = CoreDataHelper()
coreData.saveInDB(fetchedItemsFromServer)
}
}
}
According to above code this is possible to save in CoreData concurrently.
I did some tasks to resolve the problem but they didn't work.
First: I wanted to create a queue in order to handle concurrent save
let saveQueue = DispatchQueue(label: "start save in CoreData");
saveQueue.async {
DispatchQueue.global(qos: .background).async{
do {
try context.save()
} catch {
print("Failed saving")
}
}
}
The above code didn't solve the problem.
Second: The CoreDataManager was not singleton. I thought that this was the problem, so I changed it to:
class CoreDataHelper {
static let sharedInstance = CoreDataHelper()
private init() {}
static func getInstance() -> CoreDataHelper {
return sharedInstance
}
//...
}
Third: I changed the CoreData Stack setting
static var managedObjectContext: NSManagedObjectContext = {
let coordinator = persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
ConcurrencyType used to be mainQueueConcurrencyType
None of these changes resolved the problem
Crash detail:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber length]: unrecognized selector sent to instance 0xb000000000014153'
*** First throw call stack:
(0x18485cf48 0x19940ff80 0x184863c5c 0x184860c00 0x184764cac 0x18482c978 0x18476de38 0x18460f134 0x1845ebda0 0x1845ed47c 0x18453f2fc 0x184509f04 0x1845d0fdc 0x1845d8fd8 0x1025b5c68 0x1025c181c 0x1845cb830 0x184509ab0 0x18452e1d8 0x1005bce00 0x10029e498 0x100484a08 0x1003c097c 0x1003ca13c 0x1001296e8 0x1025b5ca8 0x1025b5c68 0x1025bb710 0x1848141f8 0x184812060 0x184740ca0 0x18f97c088 0x189e58ffc 0x10011c8e0 0x199c5e8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
import UIKit
import CoreData
class CoreDataHandler: NSObject {
func getContext() -> NSManagedObjectContext{
let appdelegate = UIApplication.shared.delegate as! AppDelegate
return appdelegate.persistentContainer.viewContext
}
func saveDataToCoreData(username:String,password:String) -> Bool{
let entity = NSEntityDescription.entity(forEntityName:"Entity", in: getContext())
let manageObect = NSManagedObject(entity: entity!, insertInto: getContext())
manageObect.setValue(username,forKey:password)
do{
try getContext().save()
return true
}
catch{
return false
}
}
func fetchAllData() -> [Entity]?{
do{
let result:[Entity]? = try getContext().fetch(Entity.fetchRequest())
return result
}
catch{
return nil
}
}
func delete(ManagedObj:Entity) -> Bool{
getContext().delete(ManagedObj)
do{
try getContext().save()
return true
}
catch{
return false
}
}
func cleanMyCoreData() -> Bool{
let delete = NSBatchDeleteRequest(fetchRequest:Entity.fetchRequest())
do{
try getContext().execute(delete)
return true
}
catch{
return false
}
}
func filterByPredicate(pred:NSPredicate) -> [Entity]?{
let fetchReq:NSFetchRequest<Entity> = Entity.fetchRequest()
fetchReq.predicate = pred
do{
let entity = try getContext().fetch(fetchReq)
return entity
}
catch{
return nil
}
}
}
I am rewriting my previous objC app in Swift 2.2. This is cocoa application where I am using NSArrayController to fill NSTableView contents.
The error is obvious, although similar setup worked in Objective C app.
Here is my AppDelegate:
var coreStack:AP_CoreDataStack!
var mainContext:NSManagedObjectContext!
override func awakeFromNib() {
coreStack = AP_CoreDataStack(){ (result) -> () in
if result {
self.mainContext = self.coreStack.mainContext
}
}
}
Setup of Core Data Stack
// MARK: - AP_CoreDataStack Class
class AP_CoreDataStack {
let mainContext: NSManagedObjectContext
let mastercontext: NSManagedObjectContext
var workerContext: NSManagedObjectContext?
internal typealias CallBack = (result:Bool) -> Void
init ( callback: CallBack) {
let modelURL = NSBundle.mainBundle().URLForResource("appNameSWIFT", withExtension: "momd")
if (modelURL == nil) {
print("Failed to initialize modelURL: \(modelURL)")
}
let mom = NSManagedObjectModel(contentsOfURL: modelURL!)
if mom == nil {
print("Failed to initialize model")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom!)
mastercontext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
mastercontext.persistentStoreCoordinator = psc
mainContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
mainContext.parentContext = mastercontext
// add store to psc in background thread
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
//BACKGROUND THREAD
// adding store to persistent store coordinator
let options = [NSInferMappingModelAutomaticallyOption:true,
NSMigratePersistentStoresAutomaticallyOption:true]
do {
// store = try psc.addP
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: applicationDocumentDirectory(), options: options)
} catch let error as NSError {
print("Error: Failed to load store \(error.localizedDescription), \(error.userInfo)")
}
// MAIN THREAD
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// On Main thread pass message that stack setup is complete
callback(result: true)
})
})
}
Above is the Swift version of my Obj C code which worked fine. I have an NSArrayController in xib file which is bound to Entity and NSManagedObjectContext in IB:
// Bind To Delegate
self.mainContext
It seems Array controller is accessing mainContext before it is initialised, but this is the same setup which worked in objC, so why it is causing error in Swift.
EDIT: I am using regular xib file.
EDIT 2:
Evidently mainContext is not nil as calling it here works correctly
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
let request = NSFetchRequest(entityName: "AP_EntityA")
let list:Array<AnyObject>
do {
list = try coreStack.mainContext.executeFetchRequest(request)
for item in list {
let product = item as! AP_EntityA
print("item name is: \(product.uniqueName)")
}
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
Add the dynamic keyword to make Swift properties KVO compliant.
dynamic var mainContext:NSManagedObjectContext!
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.