A Core Data text asset has got a transient attribute textStorage
which is getting persistently saved in contents
of type NSData
. In some cases saving the context after editing textStorage
Core Data doesn't save the changes. The app is using BSManagedDocument
with parent managed object context to save in the background. Below is a pseudo log to illustrate what's happening. The second log is expected to be persistently saved however the parent context reverts back to the original.
Is this down to the way I've chosen for handling the encoding/decoding in the TextAsset implementation (see below)?
Original text storage before editing:
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
THIS GETS DELETED
Donec ullamcorper nulla non metus auctor fringilla.
Text storage in willSave:
in the child (main document) context (after edited in text view):
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
Donec ullamcorper nulla non metus auctor fringilla.
Text storage in willSave:
in the parent (backround saving) context (after child context got saved):
Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.
THIS GETS DELETED
Donec ullamcorper nulla non metus auctor fringilla.
As you can see the 'THIS GETS DELETED' line is still in there.
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"contents"]) {
return [keyPaths setByAddingObject:@"textStorage"];
}
return keyPaths;
}
- (void)setTextStorage:(NSTextStorage *)textStorage {
[self willChangeValueForKey:@"textStorage"];
[self setPrimitiveValue:textStorage forKey:@"textStorage"];
[self didChangeValueForKey:@"textStorage"];
}
- (NSTextStorage *)textStorage
{
[self willAccessValueForKey:@"textStorage"];
NSTextStorage *textStorage = [self primitiveValueForKey:@"textStorage"];
[self didAccessValueForKey:@"textStorage"];
if (textStorage == nil) {
NSData *contents = [self contents];
if (contents != nil) {
textStorage = [NSKeyedUnarchiver unarchiveObjectWithData:contents];
[self setPrimitiveValue:textStorage forKey:@"textStorage"];
}
}
return textStorage;
}
- (void)willSave
{
NSTextStorage *textStorage = self.textStorage;
if (textStorage != nil) {
[self setPrimitiveContents:[NSKeyedArchiver archivedDataWithRootObject:textStorage]];
}
else {
[self setPrimitiveContents:nil];
}
[super willSave];
}
After looking into it further it turns out that the issue gets caused when the contents
value of the object in the child context gets pushed to the parent up on save. If the managed object in the parent context is still in memory it will itself still have a the transient textStorage
in memory. Hence with the code above it will just archive the textStorage
again rather than using the updated contents
.
By resetting the textStorage
when (primitive) contents
changes this behaviour can be avoided.