Nicolas Miari
Nicolas Miari

Reputation: 16246

NSManagedObject Subclass Name Collides with Swift Foundation Type

The code I’m working on now (inherited from another team) has a persistence layer based on CoreData.

One of the entities is named “Notification”, and it represents messages periodically polled by the client app from the back end (unrelated to APS); the corresponding NSManagedObject subclass is defined the following way:

import Foundation
import CoreData

@objc(Notification) public class Notification: NSManagedObject {

    // etc...

Needless to say, this class name shadows the homonymous Foundation type (counterpart to NSNotification on the Objective-C side). The app does not use the NotificationCenter machinery, so it wasn't a problem until now.

Now I need to introduce notifications for some of my classes to observe certain app-level events, and I don’t want to have to disambiguate each and every time, e.g.

let notification = Foundation.Notification.Name(...

I am aware that I cannot change the class names of my CoreData entities without breaking compatibility, but I thought that the @objc(Notification) would let me change the Swift class name; for example:

@objc(Notification) public class AppNotification: NSManagedObject {
//    ^ This stays the same      ^ This changes

...after all, CoreData is an Objective-C API. It makes sense that, if I have to explicitly specify the Objective-C bridged class name, perhaps I can get away with a Swift class name that is different from the model.

But no, my app crashes if I make the change above.

Is there a way around this, or am I stuck with the horrible decision (and lack of foresight) of the original author?

Upvotes: 1

Views: 134

Answers (1)

zrzka
zrzka

Reputation: 21219

Initial state

Let's say that you have the following class in Swift:

@objc(Notification)
class Notification: NSManagedObject {

}

extension Notification {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<Notification> {
        return NSFetchRequest<Notification>(entityName: "Notification")
    }
}

extension Notification : Identifiable {
}

And the following entity in your model:

enter image description here

Change class name

Can I change class name? Sure. Just update your code:

@objc(MyNotification)
class MyNotification: NSManagedObject {
}

extension MyNotification {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<MyNotification> {
        return NSFetchRequest<MyNotification>(entityName: "Notification")
    }
}

extension MyNotification : Identifiable {
}

And your model:

enter image description here

It's important to keep the Entity Name set to Notification. There's no need for migration if you keep the Entity Name untouched.

Other comments

  • You should include the crash & stack trace in the question if it crashes after change like this one.
  • It's possible that it crashes because of another problem in the code.
  • I saw that people are still using className for entity name, which is utterly wrong, because NSManagedObject subclass class name can differ from the entity name. Perhaps, this is your problem? Don't know.
  • This is one of the reasons we have NSEntityDescription & name.
  • Also you probably noticed that I'm using Manual/None in the Codegen. That's because I wanted to avoid any code generation interference when I was testing the class name change.
  • You can change the Entity Name as well, but then you'll need migration.

Upvotes: 3

Related Questions