CoreData creating an NSManagedObject Subclass with a Parent Entity - ios

We have an app in Swift that has 10-12 data models. The developer who set them up generated them in the .xcdatamodeld file and I've since wrote extensions for them.
I've gotten to the point in the app where I realized that he had failed to generate a subclass for one of the data models named "Files". I tried adding it using the Editor -> Create NSManagedObject Subclass, and upon the wizard's completion, no error appears, but the subclass also does not appear. Assuming it might be a bug in XCode, I wrote my own Files subclass following the format of the others. Its pretty basic:
import Foundation
import CoreData
class Files: NSManagedObject {
#NSManaged var localUrl: String
#NSManaged var remoteUrl: String
#NSManaged var type: String
#NSManaged var translationFromAudio: NSSet
#NSManaged var translationToAudio: NSSet
}
Following this I went along parsing the JSON for our project and pushing the data into coreData
let entityDescription: AnyObject = NSEntityDescription.entityForName("Files", inManagedObjectContext: context)!
var request = NSFetchRequest(entityName: "Files")
request.predicate = NSPredicate(format: "remoteUrl == %#", urlString)
var file:Files
var results = context.executeFetchRequest(request, error: nil)! as NSArray
if results.firstObject != nil{
file = results.firstObject as Files
}else{
file = Files(entity: entityDescription as NSEntityDescription, insertIntoManagedObjectContext: context)
}
file.remoteUrl = audioString
file.type = "Institutional"
//All the other properties on a file are optional, so I did not include them
All works fine and dandy (this is the same format we used to save everything else into CoreData just with File's properties substituted in) The context.save() is ran after all parsing is complete to speed up the app, but it is called.
The crazy stuff starts happening when I try and run a FetchRequest to get all the Files from coreData. It comes back as 0 objects, but all the other coreData models come back.
I then tried deleting the app off the simulator and reloading it, and upon doing this after parsing again, nothing is saved to core data at all.
I've rolled back my code twice, and carefully did everything and the same result happens. I'm guessing this could be connected to the original issue of the Files subclass not generating properly. Any info on how to refresh your subclasses, or ideas on what I could be doing wrong are appreciated, I'm fairly new to Swift and XCode, so everything is still very new to me. Thanks!
EDIT
The solution below fixed my original problem, but I am left with two new issues:
Why can't I auto-generate subclasses from the menu Editor->Create NSManagedObject Subclass.
How should I format my subclass "Files" to inherit from a parent entity as the original functionality dictated.

So, I've fixed it, and with the fix created more questions.
In the entity editor, I selected the entity "Files", and in the Entity menu on the right hand side (the third tab on the righthand menu, I noticed that the Parent Entity Dropdown, had a parent entity selected, whereas all the others had "no parent entity". Upon doing this, deleting my app off the simulator and refreshing, functionality was restored and CoreData resumed its normal function.
This has opened two follow up questions:
1. Why can't I auto-generate subclasses from the menu Editor->Create NSManagedObject Subclass.
2. How should I format my subclass "Files" to inherit from a parent entity as the original functionality dictated.

Related

No NSEntityDescriptions in any model claim the NSManagedObject subclass

I am new to CoreData and I'm trying to create a caching mechanism wherein after parsing objects from the API, I save them to the data model then fetch it again to show it on the tableview. I'm trying to fetch it using NSFetchedResultsController. Upon initialization of the NSFetchedResultsController, I'm encountering this runtime exception:
2018-12-09 15:03:20.493509+0800 [5184:148001] [error] error:
No NSEntityDescriptions in any model claim the NSManagedObject subclass
'Product' so +entity is confused. Have you loaded your
NSManagedObjectModel yet ?
CoreData: error: No NSEntityDescriptions in any model claim the
NSManagedObject subclass 'Product' so +entity is confused. Have you
loaded your NSManagedObjectModel yet ?
2018-12-09 15:03:20.493718+0800[5184:148001] [error] error: +
[Product entity] Failed to find a unique match for an
NSEntityDescription to a managed object subclass
CoreData: error: +[Product entity] Failed to find a unique match for an
NSEntityDescription to a managed object subclass
What could be the reason why?
If you ever encounter an issue similar to this using SwiftUI, you can try changing the entity's class module from Global Namespace to Current Product Module.
Go to your xcdatamodeld file and select the problematic entity. Then in the data model inspector, change the Module field from the default Global namespace to available value "Current Product Module" by clicking on the arrow at the right of the field.
This allowed my app to compile without encountering the error.
My experience:
The entity class was missing the #objc(Person) line above the class name.
I do have more classes that are working without this line but only when creating this specific entity, I have got this error.
#objc(Person)
public class Person: NSManagedObject {
}
I came across the same issue when I changed the Codegen property of a Core Data Model to the type Category/Extension, to create a custom class for the Core Data Model.
As pointed out by #dypbrg, changing the following code segment
Product.fetchRequest()
to the following code segment
NSFetchRequest<Product>(entityName: "Product")
seems to solve the issue.
In my case I was using it SwiftUI the NSManagedObjectContext was being used before persistent store async function returned.
I then followed the core data sample project with Xcode to solve the issue:
#main
struct TestCoreDataApp: App {
//Inside the PersistenceController initialiser the store is loaded, so gives it time before passing the context to the SwiftUI View
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
In my case, I had changed the name of an entity and the swift-class but had forgot to update to the new swift-class name in the xcdatamodeld.
(In xcdatamodel view, under "CONFIGURATIONS" in the left pane, select "Default" and make sure the name of the classes are correct.)
For anyone who has this problem with hybrid projects (Obj-C + Swift).
In my case, CoreData xcdatamodel is delivered as an asset inside SPM package and is used inside this package. With Swift projects there are no problems but with hybrid one I faced the same issue.
The solution that solved my problem:
use #objc annotation for NSManagedObjects as mentioned by #Sam
#objc(Entity)
public class Entity: NSManagedObject {
}
Use Global namespace instead of Current Product Module for each entity (check #Pomme2Poule's answer how to do that)
For me, I had not named the NSPersistentContainer in the AppDelegate the same as the xcdatamodelId I had created.
let container = NSPersistentContainer(name: "DataModel")
I've encounter same issue and finally locate the root cause at persistentContainer lazy initilaize.
In AppDelegate.swift, remove lazy works for me.
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {...}

best way to rename a class in Realm swift

I use a "common" library in my iOS project. This library creates a Realm database. So far, I've been using this library on only iOS projects. I want to now use that same library with a macOS project. It's Foundation based, and doesn't use UIKit, so why not?
Here's the problem: I have a Realm class named Collection
Collection is also the name of a standard Swift protocol.
While I've been able to get away with this name collision on my iOS project, for some reason, I can't do the same on my MacOS project -- it creates a name-collection.
I read about this notation that can be used like this:
#objc(SpecialCollection)
class Collection: Realm.Object {
let items: List<ItemObject>
let name: String
let url: String
....
}
So, this solves the name-collision problem. In ObjC, the name will be something different, but in Swift, I don't need to change anything.
This is all well and good except for my local Realm database. I have a lot of Collection objects that should be renamed to SpecialCollection (since Realm uses ObjC underneath Swift). I'd like to perform a migration to do this, but apparently there isn't a supported way to do this yet? I noticed tickets on github about this issue being "watched", but unfortunately, there still exists no published solution to fix this problem.
All of my Collection objects contain List objects (hence the name). So, I tried to run an enumeration on all of the Collection objects in a migration... I would just take the older object, and create a new object with the new name, like this:
migration.enumerateObjects(ofType: "Collection", { (oldObject, _) in
migration.create("SpecialCollection", value: oldObject)
}
But since oldObject has a list of other objects, Realm's migration will try and create all the items in any List objects... which can't be done, because it creates objects with the same primaryKey value (causing a crash).
So, I can't keep the old name (Collection), and I can't convert to the new name, and I can't just trash the user's data. So, I'm truly at an impasse.
Blockquote
I tried to modify oldObject before creating the new object, but you can't change oldObject in a migration.
The only rule is that the old data has to be preserved, I can't just destroy the user's realm here.
Thanks for any help in this. It is greatly appreciated.
I had a very similar problem last night. I had a couple Realm classes I wanted to rename, and where one of them had a List property referring to the second class. So the only difference compared to your problem is I was renaming ItemObject class as well.
So here's how I did it:
Migrate your Collection class first, creating SpecialCollection.
While migrating, walk the Collection's list and create new
SpecialItemObject for each ItemObject and append it to the new list.
Delete each ItemObject.
Now enumerate all ItemObject remaining
in the realm and create a new SpecialItemObject and map its values
over. The reason is there may be other ItemObject floating around
in your realm, not tied to the list.
Delete all remaining ItemObject.
migration.enumerateObjects(ofType: "Collection")
{ (oldObject, newObject) in
let specialCollection = migration.create(SpecialCollection.className())
specialCollection["name"] = oldObject!["name"]
specialCollection["url"] = oldObject!["url"]
if let oldItems = oldObject!["items"] as? List<MigrationObject>,
let newItems = specialCollection["items"] as? List<MigrationObject>
{
for oldItem in oldItems
{
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"] // You didn't specify what was in your ItemObject so this example just assumes a name property.
newItems.append(newItem)
migration.delete(oldItem)
}
}
}
migration.deleteData(forType: "Collection")
// Now migrate any remaining ItemObject objects that were not part of a Collection.
migration.enumerateObjects(ofType: "ItemObject")
{ (oldObject, newObject) in
let newItem = migration.create(SpecialItemObject.className())
newItem["name"] = oldItem["name"]
}
// Now let's be sure we'll have no further ItemObject in our entire Realm.
migration.deleteData(forType: "ItemObject")
So this is how I solved it for myself last night, after finding next to nothing about most of this in cocoa-realm in GitHub or on SO or elsewhere. The above example only differs from what you asked in that you weren't asking to rename your ItemObject class. You could try just creating new ItemObject objects and mapping the properties across in the same way I show in my example. I don't see why it wouldn't work. I've provided my example exactly how I solved my own problem, since I tested some migrations last night to prove it was solid.
Since your question is almost 5 months old, I'm really just posting this answer for posterity. Hope this helps someone!
Tested with Realm 3.3.2 on iOS 11.3 sim / Xcode 9.3 / Swift 4.1

Core data Issues with SpriteKit [duplicate]

I am duplicating an existing Objective-C TV Show app to a new Swift version using Xcode 6.1 and am having some issues with CoreData.
I have created a model of 4 entities, created their NSManagedObject subclass (in Swift), and all files have the proper app targets set (for 'Compile Sources').
I am still getting this error whenever I try to insert a new entity:
CoreData: warning: Unable to load class named 'Shows' for entity
'Shows'. Class not found, using default NSManagedObject instead.
A few comments:
When saving to Core Data, I use the parent-child context way to allow background threading. I do this by setting up the ManagedObjectContext using:
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
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
and by saving data using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
var context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
context.parentContext = self.managedObjectContext!
...rest of core data saving code here...
})
This warning is one of the quirks we have to deal with while the details of the Swift implementation are being ironed out. The warning occurs spuriously, i.e. your setup might work even if you do not follow the steps outlined below.
I have been able to get rid of it in most cases by making sure that the class is set correctly in the model editor. Unlike in many other SOF posts (including answers to this question), the suggestion to include the module name (like MyApp.Shows) has not helped me.
Make sure you check these three items:
1.
Version that works up to Xcode 7 beta 3
Notice that I corrected your entity name to the more appropriate singular.
Version that works for Swift 2.0 in Xcode 7.1
(Should work for Xcode 7 beta 4 and above)
You need to delete the text "Current Product Module" in Module!
2.
You should also follow the frequent recommendation to include
#objc(Show)
just above your class.
Note: If you are using Xcode 7 beta 4 or later, this step is optional.
3.
Also make sure to cast the created managed object to the proper class, as the default would be just NSManagedObject.
var newShow = NSEntityDescription.insertNewObjectForEntityForName("Show",
inManagedObjectContext: context) as Show
SWIFT 2 / XCODE 7 Update:
This issue (see my April 3 comment on this answer as well) is resolved in Swift 2 and XCode 7 beta release by Apple.
So you actually now do not need #objc(myEntity) in Swift as answered by Mundi or using
"MyAppName." before your Class name. It will stop working. So remove these, just put Class name in File and select Current Working Module as Module
and cheers!
But for those using #objc(myEntity) in Swift (like me), you can use this other solution instead which works smoothly.
In the xcdatamodel correct class in. It should look like this:
Here you go. Module.Class is the pattern for CoreData in Swift and XCode 6. You will also need the same procedure when using Custom Policy class in Model Policy or other CoreData stuff. A note: In image, The Name and Class should be Car and MyAppName.Car (or whatever the name of your entity). Here, User is a typo.
When using Xcode 7 and purely Swift, I actually had to remove #objc(MyClass) from my auto-generated NSManagedObject subclass (generated from Editor > Create NSManagedObject Subclass...).
In Xcode 7 beta 2 (and I believe 1), in the model configuration a new managed object of type File is set to the Module Current Product Module and the class of the object is shown in configuration as .File.
Deleting the module setting so it is blank, or removing the full stop so the class name in configuration is just File are equivalent actions, as each causes the other change. Saving this configuration will remove the error described.
In Xcode 6.1.1 you do not need to add the #objc attribute since the base entity is a subset of an objc class (NSManagedObject) (see Swift Type Compatibility. In CoreData the full Module.Class name is required. Be aware the Module name is what is set in Build Settings -> Packaging -> Product Module Name. By default this is set to $(PRODUCT_NAME:c99extidentifier) which will be the Target's name.
With xCode 7 and Swift 2.0 version, you don't need to add #objc(NameOfClass), just change the entity settings in "Show the Data Model Inspector" tab like below -
Name - "Your Entity Name"
Class - "Your Entity Name"
Module - "Current Product Module"
Code for Entity class file will be like (in my code Entity is Family) -
import UIKit
import CoreData
class Family: NSManagedObject {
#NSManaged var member : AnyObject
}
This example is working fine in my app with xCode 7.0 + swift 2.0
Do not forget to replace PRODUCT_MODULE_NAME with your product module name.
When a new entity is created, you need to go to the Data Model Inspector (last tab) and replace PRODUCT_MODULE_NAME with your module name, or it will result a class not found error when creating the persistent store coordinator.
You also need to use (at least with Xcode 6.3.2) Module.Class when performing your cast for example:
Assuming your module (i.e. product name) is Food and your class is Fruit
let myEntity = NSEntityDescription.entityForName("Fruit", inManagedObjectContext: managedContext)
let fruit = NSManagedObject(entity: myEntity!, insertIntoManagedObjectContext:managedContext) as! Food.Fruit
Recap:
Include module name when defining entity in Data Model Editor (Name: Fruit, Class: Food.Fruit)
When accessing the entity in code (i.e.SWIFT), cast it with Module.class (e.g. Food.Fruit)
I also encountered a similar problem, follow these steps to resolveļ¼š
The parent is NSManagedObject, not NSObject
The module of an
entity is default, not "Current Product Module"
Changing the Entity Class name in the Data Model editor to correspond to the class in question and adding #objc(NameOfClass) to file of each NSManagedObject right above the class declaration solved this problem for me during Unit Testing.
Most of these answers still seem to apply in Xcode 14. However, my Swift NSManagedObject subclass is included in a custom framework. So what worked for me is: In that Entity inspector, in that Module field (see screenshot in answer by khunsan), type in the name of your framework, for example, MyFramework.
What worked for me (Xcode 7.4, Swift) is changing the class name to <my actual class name>.<entity name>
in the Entity inspector, 'Class' box.
My initiator of the Managed object subclass, looks like this:
convenience init(<properties to init>) {
let entityDescr = NSEntityDescription.entityForName("<entity class name>", inManagedObjectContext: <managed context>)
self.init(entity: entityDescr!, insertIntoManagedObjectContext: <managed context>)}
//init properties here
For Xcode 11.5: if Codegen property is class Definition, and if you are not getting a suggestion for the entity you created in xcdatamodel. Try to quit Xcode and reopen your project again. It works for me. This answer is only if you are not getting suggestions but if your file doesn't get generated try any above answer.

How to define a Fetched Property in an NSManagedObject Subclass

In a project I am working on, we have multiple persistent stores and fetched properties are defined on the Entities to provide access to the objects that live in the different stores.
When I run Editor -> Create NSManagedObject Subclass, the fetched properties do not get populated in the subclass, and therefore are not accessible in the different controllers that use this Entity.
My curiosity is how to define these objects in the subclass so that they can be used.
For example imagine I have some object below called "Some Object", and this object has a fetched property on it called "imageFile" (The File object lives in a different store so cannot be referenced directly)
class SomeObject: NSManagedObject {
#NSManaged var name: String
#NSManaged var id: String
#NSManaged var imageID: String
#NSManaged var imageFile: File //Not generated automatically like the rest
}
Unfortunately the above attempt fails with the following error:
unrecognized selector sent to instance 0x60800865de50
So my question is a nutshell, is how do you access Fetched Properties, or what is the syntax to reference them.
Please no answers saying "Don't use Fetched Properties" or "Use only one persistent store". I already know how to use normal relationships and want to know how to utilize this feature of Core Data. Thanks in advance!
UPDATE
Trying some of the solution's posted below I ran into some interesting information that may help. I printed out the object using "po someObject" and was suprised to see the following in the output under the data attribute:
imageFile = "<relationship fault: 0x618000043930 'imageFile'>";
imageID = "some Id"
However when trying to access imageFile using someObject.imageFile I am unable to access it. Using the valueForKey["imageID"] I am able to get a reference, but it fails on the cast to File every time. When printing the object out I get:
Optional(Relationship fault for (<NSFetchedPropertyDescription: 0x6180000e1780>), name imageFile, isOptional 1, isTransient 1, entity SomeObject...
Final Update
the valueForKey["imageID"] will trigger the fault and fetch the property, I had the attributes flipped in my xcdatamodelid file, and thats why it wasn't finding it at first.
In Objective-C you could define a #dynamic property file in a
category on SomeObject, but something similar does not exist in Swift
(as far as I know).
So the only possibility is to use Key-Value coding to retrieve the
fetched property (which is always represented as an array):
if let files = yourObject.valueForKey("imageFile") as? [File] {
// ...
}
Of course you can wrap this into a computed property as suggested
in #gutenmorgenuhu's answer.
If you want to add this to the same class as the NSManagedObject, you can use the extensions feature:
extension SomeObject{
var imageFile: String {
get {// Code to return your fetchedProperty
}
}
}

How come I can cast to NSManagedObject but not to my entity's type?

I'm using the Swift boilerplate code for Core Data in a fresh project. My .xcdatamodeld file has a single entity defined (Task) with a single attribute (name).
I have a Task.swift file that looks like this:
import CoreData
class Task: NSManagedObject {
#NSManaged var name: String
}
When I run this, it works:
var firstTask = NSEntityDescription.insertNewObjectForEntityForName("Task",
inManagedObjectContext: managedObjectContext) as NSManagedObject
firstTask.setPrimitiveValue("File my TPS reports", forKey: "name")
var error: NSError?
managedObjectContext.save(&error)
I can even go into the SQLite database being used by the iOS simulator and confirm that the row was added.
However, when I run the exact same code as above but with as Task instead of as NSManagedObject, I get a crash with the error message Thread 1: EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0), associated with the var firstTaskā€¦ line. If I continue execution, I get EXC_BAD_ACCESS and 0 misaligned_stack_error_ at the top of Thread 1 each time I advance it.
Why might this cast lead to all this?
Make sure your Class name field is actually Module.Task, where Module is the name of your app. CoreData classes in Swift are namespaced. Right now, your object is being pulled out of the context as an NSManagedObject, not as a Task, so the as-cast is failing.
This is even frustrated if you tried all the above suggestions and non of them working for me!!
So this is what works for me.
1- Select your xcdatamodeld file
2- Make sure that all your entities has No Module in the "Data Model
Inspector", if you see "Model: Current Product Module" ..clear it it so it looks like the attached image.
3- Delete your App to clear core data
4- If still not working, delete your entities and regenerate them.
You need to modify your Task.swift file. Adding #objc(Task) like below
import CoreData
#objc(Task)
class Task: NSManagedObject {
#NSManaged var name: String
}
I think this as a bug if your project does not contain any objective-c codes. However, you need to add that line until this fixed.
I learned it from here.
Youtube video at 11:45
I guess only changing the class field in the .xcdatamodel doesn't work anymore because I still got the following exception:
fatal error: use of unimplemented initializer 'init(entity:insertIntoManagedObjectContext:)' for class
So, I entered this code in my custom class:
init(entity: NSEntityDescription!,
insertIntoManagedObjectContext context: NSManagedObjectContext!) {
super.init(entity: entity, insertIntoManagedObjectContext: context)
}
Suddenly, it worked! The NSManagedObject is now down-castable to my custom class.
I can't really understand why this is the solution, but it works :)
An update for #Ben Gottlieb answer under XCode 7.1 and Swift 2.0
add #objc to your class. See Owen Zhao's answer. The following is an example:
#objc
class ImageRecordMO: NSManagedObject{
Open your .xcdatamodled file.
Select your entity and click the data model inspector on the right panel.
Enter the class name, see the figure below
Select your module to be Current Product Module, see the figure below.
Xcode 7 + Swift 2
Person.swift
#objc(Person)
class Person: NSManagedObject {
}
Data model
Then simply call
let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: self.managedObjectContext) as! Person
I had to add the #objc() and set the class in the coredata interface.
In the CoreData right window, in the Entity area, there are Name and Class textbox. Class must not be MSManagedObject but instead your class.
I created a simple working example!
I've run into this problem in the last few days and strangely the solution that worked for me was a variant of that suggested above.
I added the #objc declaration to the generated subclasses, but removed any namespace prefix in the class name in the object model (it had a default prefix of "PRODUCT_MODULE_NAME." after the subclasses were generated). That worked.

Resources