Core Data iOS 10: Codegen and methods

Hello,


I'm using the update Core Data for iOS 10 with the new codegen capabilities (on Xcode 8 beta 1).

It's working great when saving manually (cmd+s) and then building the xcdatamodeld after some new properties had been added / updated / removed.

I can navigate to the properties by clicking them in the code and see the subclass extension (extension Event { }) reflects the changes made in the GUI.

But if I create new methods in the NSManagedObject subclass file generated by Xcode (class Event: NSManagedObject { }), and re generate the xcdatamodeld, the methods disappear.


Is that a bug in Xcode 8 b1 ?


Thanks,

Axel

Accepted Reply

Automatically generated code is not appropriate for editing because it is regenerated whenever the model is dirtied, which is why the files live in DerivedData.


If you want to add your own functionality to automatically generated subclasses, you should create a new extension in your own source. If you want to add properties with their own storage, you can change the code generation type to "Category/Extension" and then create a class definition file in your own source tree for your new methods.

Replies

Automatically generated code is not appropriate for editing because it is regenerated whenever the model is dirtied, which is why the files live in DerivedData.


If you want to add your own functionality to automatically generated subclasses, you should create a new extension in your own source. If you want to add properties with their own storage, you can change the code generation type to "Category/Extension" and then create a class definition file in your own source tree for your new methods.

Thanks. I noticed the generated files are written in objective-c (.h & .m) whereas my project mainly use Swift. Is there a way to force the use of Swift? Is this a settings in my Xcode project?

The language used by automatic code gen is a property of the model. There should be a "Code Generation" section with a "Language" drop down menu in the model editor's file inspector.

Thanks. I figured it out and Swift code is now generated instead of Objective-C.

Hey @sftp, this feature is not usable in Beta 3. It does not work as advertised and is not a viable replacement for mogenerator.


Specifically, no matter which codegen option you pick, you'll get a +CoreDataClass.swift file in addition to a +CoreDataProperties.swift file.


The +CoreDataClass.swift file is useless because the name isn't simply the name of your class, and if you're migrating to Xcode 8 + Swift 3 you typically already have class files that were generated by mogenerator (which of course no longer works).


The files are missing generated string constants like the Attributes and Relationships enums provided by mogenerator. These enums/structures are needed for safer KVC.


Worse, sets are not properly typed.


Far worse than that, ordered to-many relationships are not generated properly. There are no accessor methods like in mogenerator. These relationships are spit out as OrderedSet—which are not mutable. They need to be NSMutableOrderedSet or somesuch. If something gets dumped into Derived Data, then it's not visible or discoverable.


If you replace the generated types with NSMutableOrderedSet, you can get them to work but suddenly inverse relationships don't get updated when you add or remove objects. That should be a showstopper bug unless there is a documented workaround.


None of these changes are adequately documented so far, and the WWDC video "What's new in Core Data" doesn't cover what to do with these to-many relationships. So it's hard to know whether the bug is in the code or in the docs, let alone whether Apple will resolve these issues before the release date.


So basically, all but the most simple Core Data apps are probably dead in the water right now.

Thank you for your feedback.


There is a known issue in the seed where navigating away from the model file causes changes to be discarded. This is especially confusing when making changes in the inspector, and is the most common cause of a class file continuing to be generated after changing the code generation type to `Category/Extension`. An explicit ⌘S (and ⌘B) immediately after changing the codegen type should work around the issue until it is fixed.


The string enums mogenerator produces are convenient, but it is not possible for the compiler to reason about them. I've filed <rdar://problem/27554613> on your behalf on the topic of emitting something like this in Objective-C, but Swift code should use the #keypath() expression, a more general solution which is always checked for validity at compile-time.


The types that are generated by Xcode for relationships were not chosen arbitrarily. Manually changing the types is not supported. There are serious performance implications for types that seem like they should be compatible (cf Set vs NSSet), and—as you've discovered—correctness issues for types that are not (cf OrderedSet vs NSMutableOrderedSet).


The lack of generated accessors for to-many relationships is tracked by <rdar://problem/22179950>; in the meantime it should be possible for you to declare the methods yourself in an extension, using the @NSManaged keyword to inform the compiler that the implementation is expected to be provided by Core Data.

I'm not sure I understand how to use the Core Data automatic subclass generation properly (or if it works properly). I'm using Xcode beta 4 with an iOS application (and also Watchkit Extension). I currently have my Core Data model entity's Codegen setting set to "Category/Extension", and I have some code in a file Entity+CoreDataClass.swift as follows:

import Foundation
import CoreData
@objc(MyEntity)
class MyEntity: NSManagedObject {
my methods here
}


The generated code in the file "MyEntity+CoreDataProperties.swift" gets a compilation error:

import Foundation
import CoreData
extension MyEntity {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<MyEntity> {
        return NSFetchRequest<TeslaLocation>(entityName: "MyEntity");
    }
    @NSManaged public var address: String?
}

The error is on the "@nonobj public class..." line above, and the error message is

Method cannot be declared public because its result uses an internal type


If I manually remove the "public" keyword, it compiles. But of course this file gets regenerated sometimes, so I have to do this again.


Is this a bug or a feature?

Thanks for bringing this up, I have been asking myself the same question.

Regardless of the answer to that question, editing generated code is not a good solution—as you point out yourself. For now, it would be more resilient to work around this issue by marking your class (in Entity+CoreDataClass.swift) as public.

Thanks, stfp. Especially for the #keyPath tip. However, #keyPath is not usable with Core Data properties. At least not the way they're declared by codegen. The compiler error is:


Argument of '#keyPath' refers to non-'@objc' property 'foo'


I tried it with and without the quotes. It does work, however, if you use the form #keyPath(ClassName.property).


I see that in Beta 5, the generator spits out OrderedSet which has been renamed to NSOrderedSet, so it won't compile. And if you rename them manually, they'll compile but at runtime the newly generated accessors like addToRelationshipName() will crash with the following internal exception:


-[NSSet intersectsSet:]: set argument is not an NSSet


Is this a known bug and what's the workaround for the next two weeks? I assume it's to manage your own accessor for any ordered sets, like so:


extension Deck {
    func addCards(_ objects: NSOrderedSet) {
        let mutable = cards?.mutableCopy() as! NSMutableOrderedSet
        mutable.union(objects)
        cards = mutable.copy() as? NSOrderedSet
    }
    func removeCards(_ objects: NSOrderedSet) {
        let mutable = cards?.mutableCopy() as! NSMutableOrderedSet
        mutable.minus(objects)
        cards = mutable.copy() as? NSOrderedSet
    }
    func addCardsObject(_ value: SSPlayingCard) {
        let mutable = cards?.mutableCopy() as! NSMutableOrderedSet
        mutable.add(value)
        cards = mutable.copy() as? NSOrderedSet
    }
    func removeCardsObject(_ value: SSPlayingCard) {
        let mutable = cards?.mutableCopy() as! NSMutableOrderedSet
        mutable.remove(value)
        cards = mutable.copy() as? NSOrderedSet
    }
}


But even this begs the question: Why aren't to-many relationships generated as NSMutableSet or NSMutableOrderedSet to begin with? Surely you guys know that to-many relationships are, by definition, mutable. Can you think of a time when they aren't mutable?

There is a known issue (tracked by <rdar://problem/10114310>) where some of the generated accessors do not work correctly when using an ordered to-many relationship that has been open for some time. The OrderedSet vs NSOrderedSet issue is being tracked as <rdar://problem/27689124>.


Back on the topic of collection types; while it's tempting to model the relationship with a mutable collection type from the standard library, you wouldn't be able to interact safely with an actual instance of that type because Core Data needs to be notified about mutations so the object can be marked as dirty¹. This can be worked around using a proxy object in Objective-C, but Swift makes this story more complicated since collections are expected to be structs². The current solution of KVC-style methods on the object itself strike a balance that allows space for that magic to happen while minimizing surprise and inconvenience.


~~~~


¹: This is why data loss is one of the failure modes when mixing and matching mutable types with your modelled properties—something may be added to the collection, but the context never knows that 1) the object should not be faulted out and 2) that the object should be part of the database transaction when the context saves.

²: I think this is good philosophically, even if it makes our current existence a bit more messy

The generated accessors in Beta 6 crash at runtime.

That sounds like <rdar://problem/10114310>; the accessors are provided by the Core Data framework at runtime, which is independent of the version of Xcode used to build your product. The generated accessors should work for applications running on the latest iOS 10/Sierra betas (which have the fix).