4

as we know NSFetchRequest's propertiesToFetch can limite the properties we want, it can reduce the footprint of memory. If we do like this:

request.propertiesToFetch = [NSArray arrayWithObjects:@"nID", nil];

and:

    NSString *strID = [NSString stringWithFormat:@"%@", aPerson.nID];
cell.textLabel.text = strID;

then debug output(and not fire fault):

CoreData: sql: SELECT t0.Z_ENT, t0.Z_PK, t0.ZNID FROM ZPERSON t0 ORDER BY t0.ZNID

A strange thing happened, however, when i add a new unmodeled property to the subclass of NSManagedObject Person. Just like this:

@interface Person (Ex)
@property (nonatomic, retain) NSString *strTempName;
@end

@implementation Person (Ex)
@dynamic strTempName;

-(void)setStrTempName:(NSString *)strTempName
{
    [self setPrimitiveValue:strTempName forKey:@"strTempName"];
}

-(NSString*)strTempName
{
    return [self primitiveValueForKey:@"strTempName"];
}

And this is where the new unmodeled property is accessed:

NSString *strTT = aPerson.strTempName;

then debug output:

2013-05-06 10:43:07.789 TestCoreData[988:c07] CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZBISFORTEST, t0.ZBISINTRASH, t0.ZNID, t0.ZSTRNAME, t0.ZSTROK, t0.ZSTROK2, t0.ZSTRTEST1, t0.ZSTRTEST2, t0.ZADEPART, t0.ZAMIDDLETHUMNAIL, t0.ZANORMALPIC, t0.ZASMALLTHUMNAIL FROM ZPERSON t0 WHERE  t0.Z_PK = ? 
2013-05-06 10:43:07.790 TestCoreData[988:c07] CoreData: annotation: sql connection fetch time: 0.0010s
2013-05-06 10:43:07.791 TestCoreData[988:c07] CoreData: annotation: total fetch execution time: 0.0018s for 1 rows.
2013-05-06 10:43:07.792 TestCoreData[988:c07] CoreData: annotation: fault fulfilled from database for : 0x898a030 <x-coredata://CAD35D43-F6B2-4463-B59B-C9A3CD488935/Person/p51846>

From the above output message, we can find the unmodeled property caused that instead of SELECT t0.Z_ENT, t0.Z_PK, t0.ZNID, SELECT all of properties sentence is generated and fault is fired!

however, I have read some messages about unmodeled property(here is link):

Because unmodeled properties are only attributes of the custom NSManagedObject subclass and not the entity, the fault objects know nothing about them. Fault objects are initialized from the data model so that all the keys they respond to must be in the data model. This means faults will not reliably respond to request for unmodeled properties.

why did the strange thing happen?

thanks in advance.


@Anonymous,

choice 1): app crashed and the debug output showed:

2013-05-06 14:03:05.665 TestCoreData[1794:c07] -[Person strTempName]: unrecognized selector sent to instance 0x6ba77b0
2013-05-06 14:03:30.395 TestCoreData[1794:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person strTempName]: unrecognized selector sent to instance 0x6ba77b0'

choice 2): at first, the methods like willAccessValueForKey etc are to send KVO change notification, just like comments in NSManagedObject.h:

- (void)willAccessValueForKey:(NSString *)key;      // read notification
- (void)didAccessValueForKey:(NSString *)key;       // read notification (together with willAccessValueForKey used to maintain inverse relationships, to fire faults, etc.) 

secondly, following choice 2) code, the strange thing(to fire faults) is still alive in my app.

also thanks to your reply.

Community
  • 1
  • 1
jerrium
  • 43
  • 1
  • 1
  • 3
  • you are working on Mac OS X applications or iOS app ? – Raptor May 06 '13 at 03:11
  • @Shivan Raptor it is on iOS app simulator. – jerrium May 06 '13 at 03:25
  • Why do you use `@dynamic` when you define both getter and setter? – Sulthan May 06 '13 at 21:27
  • because @synthesize is NOT allowed in a category's implementation. – jerrium May 07 '13 at 04:10
  • @jerrium If you define both getter & setter, `@dynamic` is not needed. `@dynamic` tells the compiler: "you don't see the getter and setter but they are defined somewhere else where you can't see them, don't bother me with warnings". If you define the methods manually, there's no need to use `@dynamic`. – Sulthan May 07 '13 at 09:37

3 Answers3

0

There are things going wrong:

First: It is correct, that @dynamic is wrong. The accessors are in your code. There is nothing dynamic.

Second: Primitive accessors are for modeled properties. You have an "ivar property". Just use it the way you would use it anywhere else. (But add the …access… and …change… methods.)

After changing that, re-check, whether faults are fired.

+++ In the documenation of -didAccessValueForKey: (NSManagedObject) you will find a sample for a correct implementation:

- (NSString *)firstName
{
    [self willAccessValueForKey:@"firstName"];
    NSString *rtn = firstName;
    [self didAccessValueForKey:@"firstName"];
    return rtn;
}
Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
  • thanks for you replay. you said:Primitive accessors are for modeled properties, which is the important clue to me. And the instance variables may be placed in subclass and NOT be placed in categories.Avoiding the codes i wrote will NOT be replaced by xcode in subclass, the codes must be placed in categories.Therefore, i think the ONLY way to do is to use Associated Reference in category in order to hold instance var. – jerrium May 07 '13 at 04:06
  • @synthesize in categories is not possible, because it changes the memory footprint of instances. The RTE wants to know the size of the instances by reading the class definition. Categories can be loaded later and can not change the original memory footprint. (You need a extra level of indirection to solve that problem.) But I do not understand, why you cannot subclass NSManagedObject!? – Amin Negm-Awad May 07 '13 at 04:52
  • the class Person is the subclass of NSManagedObject, which is generated by xcode. I think i catch what you meant. Your idea is that i maintain the class Person manually when i change model(such as add, del, modify properties), instead of automatically generating the class Person. Is what i am saying your idea? – jerrium May 07 '13 at 06:05
  • It shouldn't be that much work, esp. if you plan your model good from beginnning. Chnages of the model causes more work at other places. Anyway, the "ivar properties" are not influenced by the model. (Except of the case, you have a name collision.) – Amin Negm-Awad May 07 '13 at 14:56
  • your answer and Sulthan's answer all are the real reason. Could i accept 2 answers...i am in dilemma – jerrium May 08 '13 at 06:08
  • No dilemma. I'm here to help, not to get reputation. – Amin Negm-Awad May 08 '13 at 11:03
  • Affected by you. Appreciate! – jerrium May 09 '13 at 01:45
0

return [self primitiveValueForKey:@"strTempName"];

This is wrong. primitiveValueForKey is made to access a modelled property. Since your property is not modelled, you are getting all the weird behavior - core data fires fault because it is looking for the property and when it doesn't find it, it throws an exception.

What do you want to achieve? Maybe you are looking for a transient property or maybe you should just synthesize the property like in any other object?

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • thanks for you reply. What i want is just to have a place to hold a value per an instance. No need for tracking in context and saving to DB. So, the transient property is NOT what i need. And synthesize is NOT allowed in category. It seems Associated Reference maybe is the ONLY solution. – jerrium May 07 '13 at 04:16
  • @jerrium You just cannot add data by a category, even if it's a managed object. Anyway, you can't use `primitiveValue` for unmodelled properties. If you want an umodelled property, you have to add the storage (ivar, synthesized property, associated reference) somewhere. Maybe it would be easier to store the data per object somewhere else, for example in a shared dictionary? – Sulthan May 07 '13 at 08:21
  • yes.The dictionary shared by all instances was my solution several months ago.This, however, was NOT encapsulated. Now associated reference is adopted in my code and it works well. – jerrium May 07 '13 at 09:32
  • @jerrium Note that your solution will break if you have multiple managed object contexts (e.g. to access data from different threads). There will be different managed object instances representing the same Core Data entity and they won't share the associated reference. – Sulthan May 07 '13 at 09:40
  • if the context in sub-thread is the child of main context in main-thread, once the child context save, the data related will be pushed to parent context.But the associated reference will NOT be pushed.Is it what you meant? In my app, i preload some objects on seperate context(NOT child) in sub-thread, and manually assign data to objects on main context in main thread, which avoids the risk you mentioned. – jerrium May 07 '13 at 10:00
  • Your answer also is right. I am sorry as the post could NOT accept two right answers at the same time.Thank you very much again! – jerrium May 09 '13 at 01:48
-1

I think you've coded too much. The @dynamic generates your getters and setters for you. You have two choices.

1) Remove the setter/getter calls.

2) Modify your getter/setters to warn the system that you're changing values:

-(void)setStrTempName:(NSString *)strTempName
{
    [self willChangeValueForKey:@"strTempName"];
    [self setPrimitiveValue:strTempName forKey:@"strTempName"];
    [self didChangeValueForKey:@"strTempName"];
}

-(NSString*)strTempName
{
    [self willAccessValueForKey:@"strTempName"];
    NSString *tmp = [self primitiveValueForKey:@"strTempName"];
    [self didAccessValueForKey:@"strTempName"];
    return tmp;
}
Anonymous
  • 21
  • 1