Core Data: During Migration, should use Primitive Accessors only?

  • When implementing this method:

        -createDestinationInstancesForSourceInstance:entityMapping:manager:error:

    in a subclass of NSEntityMigrationPolicy, one typically loops through attributes of the given source instance, does whatever migration logic is desired, and then sets the results as attributes of a new destination instance.

    Some time ago, I learned that one does not want to invoke the -foo and setFoo: accessors in this method, because, duh, the invoked methods may no longer exist in the current implementation of the managed object.  Life has improved since I've been careful to leave the objects typed as unsubclassed NSManagedObject instances, which forces me to use -valueForKey: and -setValue:forKey:.

    But wait, there's more.  Although these source objects log their type as Baz or whatever, they do not respond to Baz subclass methods, only NSManagedObject methods.  They are like the proxy objects that are sometimes delivered by KVO.  It's rather confusing to see an exception such as "-[Baz foo]: unrecognized selector" when your implementation of Baz clearly has a -foo, but it makes sense when you stop to consider what's going on.  Migration is sandboxxed.  The system only has the old store to work with; not the old class.  Because the entity and apparently the class are stored in the store, it knows that the object is a Baz instance, but it does not know any of the old Baz behaviors.

    The implication of this is that even -valueForKey: and -setValue:forKey: will fail if the -foo or -setFoo: accessors (which the system runs in their stead) have been overridden to perform business logic which invoke other subclass methods.  "Unrecognized selector" exceptions are raised when these other subclass methods are invoked.

    Now, one does not generally want an app's regular business logic to be run during a migration anyhow; any business logic should be implemented within the migration sandbox.  Therefore, it seems that, as a general rule, in -createDestinationInstancesForSourceInstance::::, one should access properties only via the primitive accessors -primitiveValueForKey: and -setPrimitiveValue:forKey:.

    At least I seem to have solved this morning's little programming challenge by adding "Primitive" to the accessors which were raising exceptions.

    Should I go through all of my -createDestinationInstancesForSourceInstance:::: implementations and change all accessors to the primitive accessors?  I can't find any discussion of the lameness of the source instances in the Core Data Model Versioning and Data Migration Programming Guide, and the phrase "Primitive" does not even appear in that document.

    Thanks,

    Jerry Krinock
  • On Nov 12, 2010, at 10:51 AM, Jerry Krinock wrote:

    > When implementing this method:
    >
    > -createDestinationInstancesForSourceInstance:entityMapping:manager:error:
    >
    > in a subclass of NSEntityMigrationPolicy, one typically loops through attributes of the given source instance, does whatever migration logic is desired, and then sets the results as attributes of a new destination instance.
    >
    > Some time ago, I learned that one does not want to invoke the -foo and setFoo: accessors in this method, because, duh, the invoked methods may no longer exist in the current implementation of the managed object.  Life has improved since I've been careful to leave the objects typed as unsubclassed NSManagedObject instances, which forces me to use -valueForKey: and -setValue:forKey:.
    >
    > But wait, there's more.  Although these source objects log their type as Baz or whatever, they do not respond to Baz subclass methods, only NSManagedObject methods.  They are like the proxy objects that are sometimes delivered by KVO.  It's rather confusing to see an exception such as "-[Baz foo]: unrecognized selector" when your implementation of Baz clearly has a -foo, but it makes sense when you stop to consider what's going on.  Migration is sandboxxed.  The system only has the old store to work with; not the old class.  Because the entity and apparently the class are stored in the store, it knows that the object is a Baz instance, but it does not know any of the old Baz behaviors.
    >
    > The implication of this is that even -valueForKey: and -setValue:forKey: will fail if the -foo or -setFoo: accessors (which the system runs in their stead) have been overridden to perform business logic which invoke other subclass methods.  "Unrecognized selector" exceptions are raised when these other subclass methods are invoked.
    >
    > Now, one does not generally want an app's regular business logic to be run during a migration anyhow; any business logic should be implemented within the migration sandbox.  Therefore, it seems that, as a general rule, in -createDestinationInstancesForSourceInstance::::, one should access properties only via the primitive accessors -primitiveValueForKey: and -setPrimitiveValue:forKey:.
    >
    > At least I seem to have solved this morning's little programming challenge by adding "Primitive" to the accessors which were raising exceptions.
    >
    > Should I go through all of my -createDestinationInstancesForSourceInstance:::: implementations and change all accessors to the primitive accessors?  I can't find any discussion of the lameness of the source instances in the Core Data Model Versioning and Data Migration Programming Guide, and the phrase "Primitive" does not even appear in that document.
    >
    That the objects will be fetched as NSManagedObjects is documented in the versioning & migration guide here Three-Stage Migration.  You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: accessors for properties defined in your managed object model - the accessors will perform better than valueForKey/setValue:forKey: (see Dynamically-Generated Accessor Methods)

    > Thanks,
    >
    > Jerry Krinock
  • And now with functional links... sigh

    On Nov 12, 2010, at 10:51 AM, Jerry Krinock wrote:

    > When implementing this method:
    >
    > -createDestinationInstancesForSourceInstance:entityMapping:manager:error:
    >
    > in a subclass of NSEntityMigrationPolicy, one typically loops through attributes of the given source instance, does whatever migration logic is desired, and then sets the results as attributes of a new destination instance.
    >
    > Some time ago, I learned that one does not want to invoke the -foo and setFoo: accessors in this method, because, duh, the invoked methods may no longer exist in the current implementation of the managed object.  Life has improved since I've been careful to leave the objects typed as unsubclassed NSManagedObject instances, which forces me to use -valueForKey: and -setValue:forKey:.
    >
    > But wait, there's more.  Although these source objects log their type as Baz or whatever, they do not respond to Baz subclass methods, only NSManagedObject methods.  They are like the proxy objects that are sometimes delivered by KVO.  It's rather confusing to see an exception such as "-[Baz foo]: unrecognized selector" when your implementation of Baz clearly has a -foo, but it makes sense when you stop to consider what's going on.  Migration is sandboxxed.  The system only has the old store to work with; not the old class.  Because the entity and apparently the class are stored in the store, it knows that the object is a Baz instance, but it does not know any of the old Baz behaviors.
    >
    > The implication of this is that even -valueForKey: and -setValue:forKey: will fail if the -foo or -setFoo: accessors (which the system runs in their stead) have been overridden to perform business logic which invoke other subclass methods.  "Unrecognized selector" exceptions are raised when these other subclass methods are invoked.
    >
    > Now, one does not generally want an app's regular business logic to be run during a migration anyhow; any business logic should be implemented within the migration sandbox.  Therefore, it seems that, as a general rule, in -createDestinationInstancesForSourceInstance::::, one should access properties only via the primitive accessors -primitiveValueForKey: and -setPrimitiveValue:forKey:.
    >
    > At least I seem to have solved this morning's little programming challenge by adding "Primitive" to the accessors which were raising exceptions.
    >
    > Should I go through all of my -createDestinationInstancesForSourceInstance:::: implementations and change all accessors to the primitive accessors?  I can't find any discussion of the lameness of the source instances in the Core Data Model Versioning and Data Migration Programming Guide, and the phrase "Primitive" does not even appear in that document.
    >
    That the objects will be fetched as NSManagedObjects is documented in the versioning & migration guide here:

    http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreD
    ataVersioning/Articles/vmMigrationProcess.html#//apple_ref/doc/uid/TP400055
    08-SW8


    You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: accessors for properties defined in your managed object model - the accessors will perform better than valueForKey/setValue:forKey:

    http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreD
    ata/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154-SW9


    > Thanks,
    >
    > Jerry Krinock
  • On 2010 Nov 16, at 09:48, Adam Swift wrote:

    > That the objects will be fetched as NSManagedObjects is documented in the versioning & migration guide … Three-Stage Migration.

    Thank you, Adam.  I see that it says "the class of all entities is changed to NSManagedObject".  However, I think that I'm probably not alone in wondering exactly what it means to ** change the class of an entity ** , and the programming implications of it, which I learned when exceptions were raised.

    > You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: accessors for properties defined in your managed object model -

    Yes, unless, as I found, accessors have been overridden, and the overrides invoke methods which are not defined in NSManagedObject.

    > the accessors will perform better than valueForKey/setValue:forKey: (see Dynamically-Generated Accessor Methods)

    Well, since migration only happens once in a lifetime of a database, I'm not too worried about it, unless it falls to zero, which is what happens when a "method does not respond to selector" exception is raised :(
  • On Nov 16, 2010, at 10:46 AM, Jerry Krinock wrote:

    >
    > On 2010 Nov 16, at 09:48, Adam Swift wrote:
    >
    >> That the objects will be fetched as NSManagedObjects is documented in the versioning & migration guide … Three-Stage Migration.
    >
    > Thank you, Adam.  I see that it says "the class of all entities is changed to NSManagedObject".  However, I think that I'm probably not alone in wondering exactly what it means to ** change the class of an entity ** , and the programming implications of it, which I learned when exceptions were raised.
    >
    It simply means that any class name specified in the managed object model for your entities will be ignored.  If the docs don't communicate that clearly then that would be worth filing a bug to clarify the language (it seems clear to me, but I already know how it works).

    >> You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: accessors for properties defined in your managed object model -
    >
    > Yes, unless, as I found, accessors have been overridden, and the overrides invoke methods which are not defined in NSManagedObject.
    >
    The accessors defined on your NSManagedObject subclasses will not be called - unless you've added property accessors to NSManagedObject via a category (and you REALLY don't want to do that) that call them directly.

    >> the accessors will perform better than valueForKey/setValue:forKey: (see Dynamically-Generated Accessor Methods)
    >
    > Well, since migration only happens once in a lifetime of a database, I'm not too worried about it, unless it falls to zero, which is what happens when a "method does not respond to selector" exception is raised :(
    >
    What you seem to be describing shouldn't be possible (unless you've added property accessors to NSManagedObject via a category) - the accessors defined on your subclass are not going to be called during migration because the instances being migrated are not instances of your class.

    - adam
  • On 2010 Nov 16, Adam Swift wrote:

    > What you seem to be describing shouldn't be possible (unless you've added property accessors to NSManagedObject via a category)

    I definitely have not done that.  I subclass NSManagedObject.

    Let's step back a little here:

    > You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: laccessors for properties defined in your managed object model.

    Well, -foo and -setFoo: won't even compile unless I change the type of the sourceInstance: parameter of the method to be a MyManagedObject* like this:

    - (BOOL)createDestinationInstancesForSourceInstance:(MyManagedObject*)foo
                                          entityMapping:(NSEntityMapping*)inMapping
                                                manager:(NSMigrationManager*)inManager
                                                  error:(NSError**)error_p

    or else do some equivalent typecasting in the implementation.

    > - the accessors defined on your subclass are not going to be called during migration because the instances being migrated are not instances of your class.

    Makes sense, but I distinctly remember debugging this and seeing in the call stack that KVO was calling my custom accessors.
      I'll have a closer look the next time that I run into this problem.  Making a demo project of a Core Data migration is quite tedious.

    By the way, my custom accessors are also called if I used -valueForKey: and -setValue:forKey:.  This is per documentation [1]:

    "The access pattern key-value coding uses for managed objects is largely the same as that used for subclasses of NSObject—seevalueForKey:"

    and drilling down, [2]:

    Default Search Pattern for valueForKey:
    When the default implementation of valueForKey: is invoked on a receiver, the following search pattern is used:
    • Searches the class of the receiver for an accessor method whose name matches the pattern -get<Key>, -<key>, or -is<Key>, in that order.

    [1] http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Core
    Data/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154-SW9

    [2] http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyV
    alueCoding/Concepts/SearchImplementation.html#//apple_ref/doc/uid/20000955
  • On Nov 16, 2010, at 15:55, Jerry Krinock wrote:

    >> You should be able to use the standard KVC accessors during migration, NSManagedObjects will respond to the foo/setFoo: laccessors for properties defined in your managed object model.
    >
    > Well, -foo and -setFoo: won't even compile unless I change the type of the sourceInstance: parameter of the method to be a MyManagedObject* like this:
    >
    > - (BOOL)createDestinationInstancesForSourceInstance:(MyManagedObject*)foo
    > entityMapping:(NSEntityMapping*)inMapping
    > manager:(NSMigrationManager*)inManager
    > error:(NSError**)error_p
    >
    > or else do some equivalent typecasting in the implementation.

    I think you're mixing up two different things here.

    1. Regardless of whether we're talking about migrating or just using Core Data stores, *any* NSManagedObject, including vanilla ones whose entity doesn't name a custom subclass, has accessor methods for all of the properties in the corresponding entity definition. But the compiler doesn't know that these accessors exist, so you can't use them in source unless you arrange for @dynamic declarations of them, for which there are a couple of semi-automatic procedures.

    I guess to make such declarations usable, you could just *declare* subclasses and use subclass-typed pointers in the source code, even though the class is still officially NSManagedObject.

    I guess this oddity doesn't normally come up in non-migration scenarios, because if you want to make the dynamic accessors visible to the compiler, you'd likely specify a subclass in the entity, even if you don't actually override any accessors.

    2. If you have a *custom* NSManagedObject subclass (i.e. whose subclass name is known to the Core Data entity), you can of course override the Core-Data-supplied accessor methods by writing your own. Adam was saying that there isn't supposed to be any legal way to use such custom subclasses during migration. All you've got are objects of class NSManagedObject, which notwithstanding their class have the dynamic Core-Data-supplied accessors.

    I guess this kind of blurs the boundaries of what a class is, since different instances have different APIs, but that's actually nothing new -- you could always extend the class API on a per-object basis via things like 'valueForUndefinedKey:'.

    TBH, I never thought all this through explicitly before, but this is how I think it works, unless my brain has gone into meltdown again.
  • On 2010 Nov 16, at 16:51, Quincey Morris wrote:

    > 2. If you have a *custom* NSManagedObject subclass (i.e. whose subclass name is known to the Core Data entity), you can of course override the Core-Data-supplied accessor methods by writing your own. Adam was saying that there isn't supposed to be any legal way to use such custom subclasses during migration.

    Thanks, Quincey.  My claims are that such usage should not happen, and indeed you don't want it to happen, but if you're not careful, it will happen…

    1.  If you invoke -(set)foo of -(setValue:)forKey: during a migration, and you have overridden accessors with custom accessors, Core Data will find the custom accessors and use them.

    2.  This will cause undesirable results.

    3.  To prevent Core Data from using the custom accessors during migration, use the "…Primitive…" accessors instead.
  • On Nov 18, 2010, at 6:17 AM, Jerry Krinock wrote:

    >
    > On 2010 Nov 16, at 16:51, Quincey Morris wrote:
    >
    >> 2. If you have a *custom* NSManagedObject subclass (i.e. whose subclass name is known to the Core Data entity), you can of course override the Core-Data-supplied accessor methods by writing your own. Adam was saying that there isn't supposed to be any legal way to use such custom subclasses during migration.
    >
    > Thanks, Quincey.  My claims are that such usage should not happen, and indeed you don't want it to happen, but if you're not careful, it will happen…
    >
    > 1.  If you invoke -(set)foo of -(setValue:)forKey: during a migration, and you have overridden accessors with custom accessors, Core Data will find the custom accessors and use them.
    >
    This is absolutely untrue - CoreData is not going to use instances of your custom subclass during migration - your subclasses implementation of foo/setFoo: are not going to be wired up on NSManagedObject.

    > 2.  This will cause undesirable results.
    >
    > 3.  To prevent Core Data from using the custom accessors during migration, use the "…Primitive…" accessors instead.

    This isn't necessary - but if you choose to call the primitives you need to first call willAccessValueForKey: and then after didAccessValueForKey:.  See the managed object accessor methods documentation I referred to:

    If you want to implement your own attribute or to-one relationship accessor methods, you use the primitive accessor methods to get and set values from and to the managed object's private internal store. You must invoke the relevant access and change notification methods

    http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreD
    ata/Articles/cdAccessorMethods.html#//apple_ref/doc/uid/TP40002154-SW9




  • On Nov 18, 2010, at 06:17, Jerry Krinock wrote:

    > My claims are that such usage should not happen, and indeed you don't want it to happen, but if you're not careful, it will happen…
    >
    > 1.  If you invoke -(set)foo of -(setValue:)forKey: during a migration, and you have overridden accessors with custom accessors, Core Data will find the custom accessors and use them.
    >
    > 2.  This will cause undesirable results.
    >
    > 3.  To prevent Core Data from using the custom accessors during migration, use the "…Primitive…" accessors instead.

    Well, I apologize if I'm going round in a circle here, but if you have an object that executes the overrides instead of the Core-Data-provided accessors, you must have an object of the custom subclass of NSManagedObject.

    How did such an object come to exist? Adam's point was that during migration the object is actually of class NSManagedObject, not the subclass. It shouldn't be possible to legally create an instance of the subclass.
previous month november 2010 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
Go to today