I'm attempting to set up CoreData/CloudKit on a WatchOS application using NSPersistentCloudKitContainer. However, after calling container.loadPersistentStores, the viewContext.persistentStoreCoordinator field appears to still be nil.
Here are some code samples:
Persistence.swift
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "Test")
guard let description = container.persistentStoreDescriptions.first else {
fatalError("Could not retrieve a persistent store description.")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
MainApp.swift
#main
struct EventLoggerApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
ContentView.swift
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
func createRecord() {
print(viewContext.persistentStoreCoordinator) // this is nil
let sensorReadingDescription = NSEntityDescription.entity(forEntityName: "SensorReading", in: viewContext)! // this crashes
}
}
The error I'm crashing with is:
2020-12-12 11:56:32.205433-0800 EventLoggerWatch WatchKit Extension[400:541990] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Test''
*** First throw call stack:
(0x1f4bb7dc 0x1edc9c04 0x23d68a44 0x174d34 0x174378 0x22fa9d6c 0x231422e0 0x23256ebc 0x22cb86a8 0x23256ed8 0x23256ebc 0x2327a6a4 0x2330f2dc 0x2324cdac 0x2324b6f4 0x2324b75c 0x2324bd20 0x4076df2c 0x40bd9f44 0x40763d70 0x40763e80 0x40763ce8 0x40b986cc 0x40b75abc 0x40becf04 0x40be59b4 0x1f43c4ec 0x1f43c3ec 0x1f43b74c 0x1f435dac 0x1f435574 0x235f94c0 0x40b580f8 0x40b5d5f8 0x350d9b68 0x1eed893c)
libc++abi.dylib: terminating with uncaught exception of type NSException
Please let me know if I can provide any more helpful context, and thanks in advance for the guidance!
Upon further investigation, I'm not sure this question is valid. I think the code listed above was only running for the phone target, and not the watch. There are potentially issues with the watch as well, but I will work on getting the phone working first, as the issues are probably there. Thanks to all who helped out here!
Related
I am trying to get more than one entity for my coding project at school but I have an error saying invalid redeclaration of data controller.
class DataController: ObservableObject{
let container = NSPersistentContainer(name: "Blood Sugar")
init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
class DataController : ObservableObject{
let containers = NSPersistentContainer(name: "Carbohydrates")
init(){
containers.loadPersistentStores{ description, errors in
if let errors = errors{
print("Core data failed to load: \(errors.localizedDescription)")
}
}
}
}
Since you tagged this as SwiftUI, DataController should be a struct. We use value types like structs now to solve a lot of the bugs caused by using objects in UIKit and ObjC. You can see Apple's doc Choosing Between Structures and Classes for more info.
If you use an Xcode app template project and check "Use core data" you'll see a PersistenceController struct that will demonstrate how to do it correctly. I've included it below:
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "SearchTest")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
You can create more entities in the model editor. There is usually only one NSPersistentContainer per app. The container can have multiple stores (usually sqlite databases). Then you can assign different entities to each store too. To create an instance of an entity you do that on a NSManagedObjectContext and you can choose which store to save it too, although most of the time people use one store which is the default.
I am implementing Swift CoreData into my application, However I continue to face the error 'Unrecognised selector sent to Instance' when adding data to the class set up as you can see below. I realise that this is probably a rookie error, so thanks for your patience.
func coreDataRequest(){
container = NSPersistentContainer(name: "tileData")
container.loadPersistentStores { StoreDescription, error in
if let error = error {
print("Unresolved Error \(error)")
}
}
var campaign = Campaign()
campaign.countryName = "France"
}
The Error
unrecognized selector sent to instance
Here is my XCDataModel
Have you generate Campaign() class, that you add to tileData.xcdatamodeld?
if so create a NSManagedObjectContext like this:
var context: NSManagedObjectContext = {
return persistentContainer.viewContext
}()
and then use it to create Campaign like this:
var campaign = Campaign(context: context)
campaign.countryName = "France"
then if you want to store your object you have to call save on the context:
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch {
context.rollback()
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
I recommend you to look through few tutorials, it would save your time in future. There are a lot of them, just google it.
Without seeing all your code its difficult to say what is wrong, just from what I can see the container is missing a "let" in-front unless that was declared outside the function. This is a really good tutorial on setting up and using CoreData https://www.raywenderlich.com/books/core-data-by-tutorials/v7.0/chapters/3-the-core-data-stack#toc-chapter-006-anchor-011
I'm new to swift and I have trouble with understanding how environment variables works.
In Core Data, I created new Entity called "API" with one attribute id: Int32.
Then in SwiftUI, I wanted to find maximum value of id. I wrote a request, but whenever I used passed to view as environment variable managedObjectContext, it always crashed my app/preview. Here's crash info after using NSManagedObjectContext.fetch(NSFetchRequest) (using FetchRequest gives only stacktrace with exception EXC_BAD_INSTRUCTION)
...
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
External Modification Warnings:
Thread creation by external task.
Application Specific Information:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'The fetch request's entity 0x600003c54160 'API' appears to be from a different NSManagedObjectModel than this context's'
terminating with uncaught exception of type NSException
abort() called
CoreSimulator 704.12 - Device: iPhone 11 (8356FF2A-5F0A-42F7-AA32-396FADCF2BF6) - Runtime: iOS 13.4 (17E255) - DeviceType: iPhone 11
Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff23e3dcce __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff50b3b9b2 objc_exception_throw + 48
2 CoreData 0x00007fff239c6b99 -[NSManagedObjectContext executeFetchRequest:error:] + 5004
3 libswiftCoreData.dylib 0x00007fff513b63d4 $sSo22NSManagedObjectContextC8CoreDataE5fetchySayxGSo14NSFetchRequestCyxGKSo0gH6ResultRzlF + 68
...
Keep in mind, that this error is changing depending on which project, I'm using. In my main project I had error like that:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'WebsiteAPI''
Here is the code I'm using
import SwiftUI
import CoreData
struct test: View {
private var id: Int32
#Environment(\.managedObjectContext) var managedObjectContext
var body: some View {
Text("id=\(id)")
}
public init(context: NSManagedObjectContext) {
self.id = -1
//this crashes and gives no usefull information
// let request2 = FetchRequest<API>(
// entity: API.entity(),
// sortDescriptors: [NSSortDescriptor(keyPath: \API.id, ascending: false)]
// )
// self.id = request2.wrappedValue.first?.id ?? 1
guard let context2 = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
fatalError("Unable to read managed object context.")
}
let request = NSFetchRequest<API>(entityName: "API")
request.sortDescriptors = [NSSortDescriptor(keyPath: \API.id, ascending: false)]
do {
var commits = try context.fetch(request) // OK
commits = try context2.fetch(request) // OK
//commits = try self.managedObjectContext.fetch(request) // causing crash
self.id = Int32(commits.count)
} catch let error {
print(error.localizedDescription)
}
}
}
struct test_Previews: PreviewProvider {
static var previews: some View {
guard let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext else {
fatalError("Unable to read managed object context.")
}
return test(context: context).environment(\.managedObjectContext, context)
}
}
All commented lines crash app. Why getting context from AppDelegate.persistentContainer.viewContext works just fine, but using environment variable managedObjectContext, which in my opinion should be same, doesn't work? I spent 5 hours on this, checked pretty much everything, tried a lot of things but with no success. In the end I can just keep getting context from AppDelegate, but what's wrong with environment variable? Am I missing some common knowledge or is just a bug? I'm getting headache from bugs that I'm encountering in Xcode, starting from missing autocompletion after clearing build folder to hundreds of errors after changing struct/file name on all references, despite successfully building afterwards. Restarting Xcode few times every day to make it working properly is normal for me.
Also some things I noticed, when I created FetchRequest as a variable and used it in some list inside body, it worked. The problem is only, when I'm trying to fetch things manually in code/function/init, like button action or methods onAppear, init etc. I tried to run app on both physical device and showing preview. Same effect.
I'm using Xcode 11.4 with Swift 5.
Structs like the View in SwiftUI are value types and must not init any objects in the normal way because the View structs are only created during a state change and then are gone. Thus any objects they create are lost immediately. E.g. in your init method you create NSFetchRequest, NSSortDescriptor and all the fetched objects too. A View struct is typically init every time there is a state change and a parent body runs, thus you will be creating thousands of heap objects that will fill up memory and slow SwiftUI to a crawl. These problems can be diagnosed in Instruments->SwiftUI "analysis for tracing .body invocations for View types".
Obviously we do need to objects so that's where property wrappers come in. By prefixing your object allocation with a property wrapper, then the object is created in a special way where it is only init once and the same instance is given to the new struct every time it is recreated. Which as I said happens all the time in SwiftUI, more frequently or less frequently depending on how much effort you put into organising your View struct hierarchy. Health warning: Most of the sample code currently available online puts zero effort into this and needlessly update massive view hierarchies because the designed their View structs like View Controllers instead of making them as small as possible and only having properties that are actually used in body.
To solve your problem you need to use the property wrapper #StateObject to safely init your object, and it must conform to ObservableObject so that SwiftUI can be notified that the object will be changing so that after all objects have notified it can call body which will certainly be needed, unless the developer did not use the object in their body in which case the code is badly written. The object is created once just before the View's body is called, and then every time the Viewis recreated it is given the existing object rather than creating a new one. When the view is no longer shown it is automatically deinit. UseonAppearto configure the object the first time the View appears, andonChangeto update it. Fust have a funcfetchthat supplies themanagedObjectContextand youridfetch param and in it create aNSFetchedResultsControllerperform the fetch and set thefetchedObjectson an#Publishedproperty that theViewcan use. When the object sets its items it will automatically cause theView bodyto be called again updating. SwiftUI compares the body to the previously returned body and uses the differences to render the screen (using actualUIView`s). Here is a full working example I made:
import SwiftUI
import CoreData
struct ContentView: View {
var body: some View {
NavigationView {
MasterView(name:"Master")
.navigationTitle("Master")
}
}
}
class ItemsFetcher : NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
var managedObjectContext : NSManagedObjectContext?
#Published
private(set) var items : Array<Item> = []
lazy var fetchedResultsController : NSFetchedResultsController<Item> = {
let frc = NSFetchedResultsController<Item>(fetchRequest: Item.myFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
func fetch(name:String, ascending: Bool){
fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "name = %#", name)
fetchedResultsController.fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Item.timestamp, ascending: ascending)]
try! fetchedResultsController.performFetch()
items = fetchedResultsController.fetchedObjects ?? []
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
objectWillChange.send()
items = fetchedResultsController.fetchedObjects ?? []
}
}
struct MasterView: View {
#Environment(\.managedObjectContext) private var viewContext
let name: String
#State
var ascending = false
#StateObject private var itemsFetcher = ItemsFetcher()
var body: some View {
List {
ForEach(itemsFetcher.items) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
.onDelete(perform: deleteItems)
}
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigation){
EditButton()
}
#endif
ToolbarItem(placement: .automatic){
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem(placement: .bottomBar){
Button(action: {
ascending.toggle()
}) {
Text(ascending ? "Descending" : "Ascending")
}
}
}
.onAppear() {
itemsFetcher.managedObjectContext = viewContext
fetch()
}
.onChange(of: ascending) { newValue in
fetch()
}
}
func fetch(){
itemsFetcher.fetch(name: name, ascending: ascending)
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
newItem.name = "Master"
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map {itemsFetcher.items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
I put the core data stack in its own file as shown below. I read that using dependency injection is the best way to pass the managed object context. So in each of a handful of VCs, I declare the following property:
var managedObjectContext: NSManagedObjectContext?
Now, the tricky part is getting the moc from my stack to the different VCs. Which seems like a great place for a singleton, but assuming that's a bad idea, I guess I would use the code below in CoreDataStack:
let controller = self.window!.rootViewController as! ViewController
let context = self.persistentContainer.viewContext
controller.managedObjectContext = context
But that leaves me with a few questions:
1) Where in CoreDataStack should I include the code above? In the App Delegate it would go in didFinishLaunchingWithOptions, but that's not really an option now.
2) Writing the above code for every single vc that needs a context seems bad. I guess I could loop through all the VCs. I've seen the moc passed using didSet too, but that doesn't seem quite right either.
CoreData Stack
class CoreDataStack {
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
I searched a lot for this error and was not able to find a solution.
I have my managedObject:
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
and I have a Singleton class
lazy var managedObjectContext : NSManagedObjectContext? = {
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
return appDelegate.managedObjectContext
}
return nil
}()
I have a method which is called lot of time whenever I update / delete / load data from database
class func LoadDatabase(tableName: String, predicateString: String, predicateIntValue: Int) -> [AnyObject]? {
do {
let managedObjectContext = Singleton.sharedInstance.managedObjectContext
if managedObjectContext == nil {
return nil
}
let fReq: NSFetchRequest = NSFetchRequest(entityName: tableName)
if predicateString != "" {
fReq.predicate = NSPredicate(format: predicateString, predicateIntValue)
}
var result: [AnyObject]
do {
if managedObjectContext == nil {
return nil
}
result = try managedObjectContext!.executeFetchRequest(fReq)
} catch {
return nil
}
// update exist event
if
result.count != 0 {
return result
}
} catch let error1 as NSError {
print("No values to load for table: \(error1)")
}
return nil
}
I think I have problem on concurrence access. When the app starts it stars asynchronous requests to update the database. But I also call the method LoadDatabase from varius controller. Once the asynchronous call done with updating the datable, it call another method to delete datas which are not more in remote database.
This is what I get. This does not happens always.
I tried to change to MainQueueConcurrency but now I am getting also this error:
2016-03-20 21:27:01.371 Tamil League[1070:245784] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2016-03-20 21:27:01.371 Tamil League[1070:245784] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
*** First throw call stack:
(0x24a9e2eb 0x2426adff 0x24a9e231 0x249e7fe5 0x261eb683 0x261ea063 0x261e2dfb 0x24a60b21 0x24a5ee17 0x24a5f255 0x249b1bb9 0x249b19ad 0x25c2baf9 0x28c9dfb5 0xa6d5c 0x24664873)
libc++abi.dylib: terminating with uncaught exception of type NSException
Does anyone know how to solve this?