NSPersistentDocument FetchRequest warp property crash on macOS Document App SwiftUI project - macos

Project create usinging Xcode macOS Document App template, with Use Core Data checkbox checked.
Add a Book entity to Document.xcdatamodeld
Add FetchRequest warp property to ContentView,
#FetchRequest(entity: Book.entity(), sortDescriptors: []) var books: FetchedResults<Book>
Build and Run, Crash!
Crash log from console is
2020-07-03 23:12:23.597880+0800 DocMacDemo[15236:4376209] [error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'DocMacDemo.Book' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'DocMacDemo.Book' so +entity is confused. Have you loaded your NSManagedObjectModel yet ?
2020-07-03 23:12:23.598287+0800 DocMacDemo[15236:4376209] [error] error: +[DocMacDemo.Book entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
CoreData: error: +[DocMacDemo.Book entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
2020-07-03 23:12:23.644491+0800 DocMacDemo[15236:4376209] executeFetchRequest:error: A fetch request must have an entity.
2020-07-03 23:12:23.653769+0800 DocMacDemo[15236:4376209] [error] error: The fetch request's entity 0x600003500420 'Book' appears to be from a different NSManagedObjectModel than this context's
CoreData: error: The fetch request's entity 0x600003500420 'Book' appears to be from a different NSManagedObjectModel than this context's
(lldb)
I have looking for NSPersistentDocument SwiftUI example several days, but could NOT find one.
Here are some similar or relation questions. Unfortunately, this problem is not solved.
Using #fetchRequest(entity: ) for SwiftUI macOS app crashes
SwiftUI #FetchRequest crashes the app and returns error
https://developer.apple.com/forums/thread/124656?answerId=417869022#417869022
https://developer.apple.com/forums/thread/132624
EDIT:
Upload this issue project to Github, https://github.com/donly/DocMacDemo.

This is due to emptiness of new document. As in any document-based application you have to prepare some default initial data for new document
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
in Document.swift
class Document: NSPersistentDocument {
// .. other code here
override func makeWindowControllers() {
// in case of new document create new empty book in context
// that will be shown in opened document window
let isNew = self.fileURL == nil
if isNew {
_ = Book(context: self.managedObjectContext!) // << here !!
}
let contentView = ContentView().environment(\.managedObjectContext, self.managedObjectContext!)
// ... other code here

Well, the answer of Asperi is fine but that leaves you with a maybe not needed Book in the object model. Another way is just to save the context at the same place as suggested by Asperi:
if let context = managedObjectContext {
try? context.save()
}
Then there will also be no error message from the FetchRequest.

Related

NSManagedObjectContext(): `init()` was deprecated in iOS 9.0: Use -initWithConcurrencyType

I was working through Core Data Stack in Swift - Demystified but when I got to the line
self.context = NSManagedObjectContext()
I got the warning
`init()` was deprecated in iOS 9.0: Use -initWithConcurrencyType: instead
I see that I can do one of the following for self.context =
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.ConfinementConcurrencyType)
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
but since ConfinementConcurrencyType is also deprecated now that leaves me MainQueueConcurrencyType and PrivateQueueConcurrencyType. What is the difference between these two and how should I choose which one to use? I read this documentation, but I didn't really understand.
You essentially will always have at least 1 context with NSMainQueueConcurrencyType and many contexts with NSPrivateQueueConcurrencyType. NSPrivateQueueConcurrencyType is used typically for saving or fetching things to core data in the background (like if attempting to sync records with a Web Service).
The NSMainQueueConcurrencyType creates a context associated with the main queue which is perfect for use with NSFetchedResultsController.
The default core data stack uses a single context with NSMainQueueConcurrencyType, but you can create a much better app by leveraging multiple NSPrivateQueueConcurrencyType to do any work that does not affect the UI.
Replace these two function with the following one:
lazy var managedObjectContext: NSManagedObjectContext = {
// 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
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if managedObjectContext.hasChanges {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation 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.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
}

Core Data Problems?

I am trying to use CoreData but I already went through the setup for my project and forgot to check off the box to utilize it.
Is there a way to implement the use of core data when the CoreData box was not checked previously during the setup?
If I start a new project will have to transfer a lot of information and it would be time consuming so I would like to stay on the same project and not create a new one.
To be frank, you did the right think by not checking the Use CoreData box on project creation. I feel that just bloats the project with a bunch of stuff that is easier (and more insightful) to do manually.
To be short, you can implement CoreData the same regardless of what option you selected at project creation.
Here are the steps I usually go through when I want to add CoreData support to my project (manually/programatically):
Define a Data Model
These are just NSManagedObjects which represent your application's data structure. For example, a User, Message, BlogPost, etc. I also make one for my user settings.
Example:
import CoreData
class User : NSManagedObject
{
// #NSManaged is the replacement for #dynamic when using CoreData in Swift
#NSManaged var identifier : String
#NSManaged var firstName : String?
#NSManaged var lastName : String?
// This is called when a new User object is inserted to CoreData
override func awakeFromInsert()
{
super.awakeFromInsert()
self.identifier = NSUUID().UUIDString // generate a random unique ID
}
}
Add Core Data Model
This is another file you add to your project via: File -> New-> iOS-> CoreData -> Data Model. I usually store this same xcmodeldata file in my Models project folder (along with my actual model classes).
Upon selecting this new file, you'll see the CoreData model editor. You will want to see the right-hand side inspector pane is visible (hotkey is ⌥⌘1). For the core data editor, you will also primarily use the third tab (data model inspector) which is switchable with ⌥⌘3.
Now you can add an entity object to this data model (via Add Entity at the bottom). Assuming the example above, add a User entity. With the User entity selected, add the three attributes that are defined in the above class: identifier, firstName, and lastName. They should match the class definition, using String types.
Next step is to tell CoreData that this User entity defined here maps to our actual class file. With the User selected and the data model inspector pane open, set the Name to User and Class to YourAppName.User.
This is the "gotcha" with Swift and CoreData, your classes are prefixed with the module name in order to namespace them (avoiding name collisions). The nice part is that you no longer need to add "XYZ" class prefixes to your objects.
Initialize Core Data Stack
With your data model defined, you need to initialize the CoreData stack itself (database store and context). The most basic example is a global singleton for your NSManagedObjectContext, which will be lazy-loaded when needed.
You can put this in its own Swift file (CoreDataStack.swift):
import CoreData
let managedObjectContext : NSManagedObjectContext =
{
// This is your xcdatamodeld file
let modelURL = NSBundle.mainBundle().URLForResource("MyApp", withExtension: "momd")
let dataModel = NSManagedObjectModel(contentsOfURL: modelURL!)
// This is where you are storing your SQLite database file
let documentsDirectory : NSURL! = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last as? NSURL
let storeURL = documentsDirectory.URLByAppendingPathComponent("MyApp.sqlite")
let psc = NSPersistentStoreCoordinator(managedObjectModel: dataModel!)
var error : NSError?
let store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil, error: &error)
if let error = error
{
println("Uhoh, something happened! \(error), \(error.userInfo)")
}
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.persistentStoreCoordinator = psc
context.undoManager = nil
return context
}()
Using Core Data
So now that you have a working Core Data stack, some data models defined and mapped... what next?
Let's fetch some objects!
func getUsersByFirstName(firstName: String) -> [User]
{
let fetchRequest = NSFetchRequest(entityName: "User")
// The [c] part indicates case-insensitive, "Bob" == "bob"
fetchRequest.predicate = NSPredicate(format: "firstName ==[c] %#", firstName)
var error : NSError?
let results = context.executeFetchRequest(fetchRequest, error: &error) as [User]
// Handle errors here
return results
}
Oh right, we have nothing to fetch. You can also insert objects...
func insertNewUser() -> User
{
return NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: context) as User
}
And of course you can delete objects...
func deleteUser(user: User)
{
context.deleteObject(user)
}
The key is to remember that CoreData contexts (NSManagedObjectContext) keep track of changes in memory. While you can perform these CRUD operations on a context and see the changes instantly (within the same context), they will not persist in the database until you save the changes:
func saveContext() -> Bool
{
var error : NSError?
if context.hasChanges && !context.save(&error)
{
println("Something happened when saving! \(error!), \(error!.userInfo)")
return false
}
return true
}
You can also rollback changes from the last save by using context.rollback().
Feel free to explore CoreData and experiment with the more advanced features like predicates (NSPredicate), sort descriptors (NSSortDescriptor), and setting up object relationships.
Basically all the tick box for Core Data does is add the core data framework (CoreData.framework) to your project and setup your AppDelegate.m with the core data stack, add in a data file and possibly give you a sample view controller (depending on which project type you start with).
If you want your existing project to be setup like the template would set you up, then the quickest way is to just create a new project as an example and select Core Data tick box. Open the new project and review the AppDelegate.m file and grab the code for initializing core data stack. It's about 80 lines and has a comment calling out the Core Data Stack.
Take that over to your existing project and drop it in to your AppDelegate file. Also in your existing project, add the CoreData.framework, then add in a new file (File->New File->CoreData), under Core Data called a "Data Model" file. This file is used to define the equivalent of your data schema. You select it to use the graphical controls.
Then use your sample project to review how you access the core data stack by reviewing the sample ViewController.
Note: some people are not fond of how Apple sets up the Core Data stack in the AppDelegate.m and you'll find many comments about it and how to do it better, if you search for it (I feel compelled to make this disclaimer). There are also some 3rd party libraries on GitHub that can assist you in that regard as well. (MagicalRecord,SLCoreDataStack, etc).
hope that helps!
be well!

'AppDelegate' does not have a member named 'managedObjectContext'

I have been following a guide on how to have values using CoreData in swift. I have been having the error 'AppDelegate' does not have a member named 'managedObjectContext'. The guide was using the Beta version of XCode, has this been changed and any ideas on how I can fix my problem.
#IBAction func saveButtonPushed(sender: AnyObject) {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext
var newName = NSEntityDescription.insertNewObjectForEntityForName("SavedFills", inManagedObjectContext: context) as NSManagedObject
newName.setValue("Test", forKey: "name")
newName.setValue("Test2", forKey: "password")
context.save(nil)
println(newName)
println("Saved")
}
No, it shouldn't have changed at all. The error says it. There is no managedObjectContext in your AppDelegate.swift file. If you follow a tutorial, there should be a managedObjectContext in your AppDelegate.swift file already. Maybe you've missed this step. If this isn't the case, you should either add one by yourself or copy it.
If you want to get a 'standard' managedObjectContext just create a new Swift-Project and check the Use Core Data checkbox during the creation:
New -> File->Project-> Master-Detailview Application-> Use Core Data
If you are new to CoreData and Swift, try the tutorial from raywenderlich about Swift and CoreData. It's easy and nicely written.

Swift crash with release code

Can anyone shed any light as to why the following code would crash in the release version of an Xcode 6 build but not in the debug version ?
Can I cast this as something to try and prevent this
// Check if iCloud is enabled
if let currentToken = NSFileManager.defaultManager().ubiquityIdentityToken {
// The following line causes a crash in Release version
FLOG(" currentUbiquityIdentityToken is \(currentToken)")
EDIT:
More digging and the problem was caused by this code when the "DataModel" name had been changed to something else. I would have expected the "let modelURL = NSBundle." line to have thrown an exception but it does not. Seems it was just pure coincidence that the debugger was on the "FLOG(...)" line of code when the bad access exception gets thrown.
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("DataModel", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

Unable to use reference from a Make New command in Core-data app with AppleScript support

I am able to support the Make New command of AppleScript for my app, however the returned 'specified object' (an NSUniqueIDSpecifier) for the core data managed object is useless. The following AppleScript returns the error message:
error "SpellAnalysis got an error: Invalid key form." number -10002 from level id "x-coredata:///Levels/tC5A49E01-1CE1-4ED6-8F6B-BC0AE90E279A2"
tell application "SpellAnalysis"
set thisLevel to make new «class Slev» with properties {«class Saln»:3}
get properties of thisLevel
end tell
So the newly created Levels object can not be acted upon in AppleScript. I've combed the Web for a solution to this and the closest thing I have found is Bill Cheeseman's example app, "WareroomDemo" which specifically deals with Cocoa Scriptability for Core Data apps (the Sketch example does not use Core Data). Unfortunately, it is a dated example, running only on pre-64-bit XCode and I can't actually run it--I can only look at the code. His app's Make Command may have the same limitations for all I know.
The returned 'objectSpecifier' is unable to refer to the created object either as a safe-guard against corrupting Core Data's organizing scheme, or perhaps because the returned object is an un-cashed 'fault'. I think the latter possibility is unlikely because I can force the fault to cash (by getting a property value on the managed object) , yet I get the same error message with the AppleScript.
Here is the method that creates my class:
- (id)newScriptingObjectOfClass:(Class)class forValueForKey:(NSString *)key withContentsValue:(id)contentsValue properties:(NSDictionary *)properties { // Creates a new Lesson object in response to the AppleScript 'make' command.
// Documentation for 'newScriptingObject…' states that to create a new class object when using Core Data, you intercede using the following method (or you can subclass the NSCreateCommand's 'performDefaultImplementation' method and put your NSManagedObject init code there):
if (class == [Levels class]) {
//NSLog(#"class: %#",class);
NSEntityDescription *levelsEntity = [NSEntityDescription
entityForName:#"Levels"
inManagedObjectContext:levelsDBase];
NSManagedObject *levelObject = [[NSManagedObject alloc] initWithEntity:levelsEntity insertIntoManagedObjectContext:levelsDBase];
SLOG(#"lessonObject: %#", lessonObject);
NSString *levelNumberString = [[properties objectForKey:#"levelNumber"] stringValue];
SLOG(#"levelNumberString: %#", levelNumberString);
[levelObject setValue:levelNumberString forKey:#"levelNumber"];
return levelObject; // When using Core Data, it seems that you return the newly created object directly
}
return [super newScriptingObjectOfClass:(Class)class forValueForKey:(NSString *)key withContentsValue:(id)contentsValue properties:(NSDictionary *)properties];
}
Here is my object specifier method:
- (NSScriptObjectSpecifier *)objectSpecifier {
// This NSScriptObjectSpecifiers informal protocol returns a unique ID specifier specifying the absolute string of the URI representation of this managed object. // AppleScript return value: 'level id <id>'.
// The primary container is the application.
NSScriptObjectSpecifier *containerRef = nil; // I understand that if the application is the container, this is value you use for the container reference
NSString *uniqueID = [[[self objectID] URIRepresentation] absoluteString];
return [[[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:containerRef key:#"levelsArray" uniqueID:uniqueID] autorelease];
}
The problem lies with the specifier method. The Sketch example actually uses the technique that I needed. I overlooked it many times because I didn't see how it would apply to Core Data managed objects. Instead of returning the objects uniqueID, you make it return the managedObject index using the 'indexOfObjectIdenticalTo:' method as follows:
- (NSScriptObjectSpecifier *)objectSpecifier {
NSArray *levelsArray = [[NSApp delegate] levelsArray]; // Access your exposed to-many relationship--a mutable array
unsigned index = [levelsArray indexOfObjectIdenticalTo:self]; // Determin the current objects index
if (index != (unsigned)NSNotFound) {
// The primary container is the document containing this object's managed object context.
NSScriptObjectSpecifier *containerRef = nil; // the appliation
return [[[NSIndexSpecifier allocWithZone:[self zone]] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:containerRef key:#"levelsArray" index:index] autorelease];
} else {
return nil;
}
}
Note that this method resides within a subclass of your Core Data managedObject--in this case, the 'Levels' class. The 'self' within the 'indexOfObjectIndenticalToSelf:' method refers to the current managedObject ('Levels') being handled. Also, be sure to provide the specifier (accessor) type to your 'sdef' file, like this:
<element type="level">
<cocoa key="levelsArray"/>
<accessor style="index"/>
</element>

Resources