Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
453 views
in Technique[技术] by (71.8m points)

ios - Core Data not saving transformable NSMutableDictionary

I have two classes: Profile and Config. A Profile contains an NSSet of Config objects. Both Profile and Config are NSManagedObject subclasses.

@interface Profile : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSSet *configs;

- (void)print;

@end

Here is the Config class

@interface Config : NSManagedObject

@property (nonatomic, retain) NSString * otherdata;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSMutableDictionary *myDict;
@property (nonatomic, retain) Profile *profile;

- (void)print;

@end

The dictionary myDict has NSString *keys and values. Now when I make any changes to myDict, I call the NSManagedObject save method and that works fine with no errors. As long as I do not kill the app, everything behaves as expected.

But when I force kill the app (either in Xcode or by double pressing the home button and killing it in the row of buttons at the bottom) and then restart the app, the data in myDict reverts to what was there before, ie the new data was not actually saved. It only appeared to be saved before I killed the app.

myDict is listed as Transformable inside the xcdatamodeld file. I tried it without specifying any NSTransformer class. I also tried it specifying a transformer class MyDictTransformer, and in Config I added this code:

In Config.h:

@interface MyDictTransformer : NSValueTransformer

@end

In Config.m:

@implementation MyDictTransformer

+ (Class)transformedValueClass
{
    return [NSMutableDictionary class];
}

+ (BOOL)allowsReverseTransformation
{
    return YES;
}

- (id)transformedValue:(id)value
{
    return [NSKeyedArchiver archivedDataWithRootObject:value];
}

- (id)reverseTransformedValue:(id)value
{
    return [NSKeyedUnarchiver unarchiveObjectWithData:value];
}

@end

Also at top of Config.m I have this:

//
// from: http://stackoverflow.com/questions/4089352/core-data-not-updating-a-transformable-attribute
//

+ (void)initialize {
    if (self == [Config class]) {
        MyDictTransformer *transformer = [[MyDictTransformer alloc] init];
        [NSValueTransformer setValueTransformer:transformer forName:@"MyDictTransformer"];
    }
}

Also in AppDelegate, in both applicationDidEnterBackground and applicationWillTerminate I call saveContext:

- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil)
    {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }
}

No matter what I have tried, it simply does not save the dictionary in Config. It saves any other changes in things like config.name but not in config.myDict.

A) What am I doing wrong? B) How can I fix this, even if I have to use some other data structure than an NSMutableDictionary to save the data?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You've declared myDict as an NSMutableDictionary which is a big red flag.

Managed object attributes should never be a mutable type

Chances are, you're using myDict something like this:

someConfig.myDict[@"someKey"] = @"someValue";
[context save:&error];

The problem is, you're not calling a setter method of someConfig so you've done nothing to inform it that an attribute has been changed. And even though you're calling save:, the context doesn't bother saving unchanged objects.

Strictly speaking, you could probably get away with calling [someConfig didChangeValueForKey:@"myDict"] every time you change myDict. I wouldn't recommend it though because it's easy to forget and error prone.

It would better to declare myDict as non-mutable and use it like this:

@property (nonatomic, retain) NSDictionary *myDict;
...
NSMutableDictionary *updatedDict = [someConfig.myDict mutableCopy];
updatedDict[@"someKey"] = @"someValue";
someConfig.myDict = [updatedDict copy];
[context save:&error];

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...