Say you have a one-to-many relationship (modeled via Core Data) between something like a tweet and a bunch of photos:
@implementation Tweet
- (void)updateWithDictionary:(NSDictionary *)JSONDictionary {
self.photos = [JSONDictionary["photos"] transformedArrayUsingBlock:^Photo *(NSDictionary *photoJSON) {
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];
Photo *photo = [[Photo alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:self.managedObjectContext];
[photo updateWithDictionary:photoJSON];
return photo;
}];
}
This update
method could theoretically be called on the same Tweet
object multiple times, as the user refreshes their timeline. The code above would result in Photo
objects being orphaned as new ones are created to take their place.
I can think of three decent ways to avoid this:
This would entail:
- Creating identifiers for each photo
- Updating photos that already exist
- Creating photos that don't already exist
- Deleting photos that exist on disk but not in the JSON response
Seems like more work than it's worth to be honest
- (void)updateWithDictionary:(NSDictionary *)JSONDictionary {
[self.photos enumerateObjectsUsingBlock:^(Photo *photo, NSUInteger photoIndex, BOOL *stop) {
[self.managedObjectContext deleteObject:photo];
}];
self.photos = [JSONDictionary["photos"] transformedArrayUsingBlock:^Photo *(NSDictionary *photoJSON) {
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Photo" inManagedObjectContext:self.managedObjectContext];
Photo *photo = [[Photo alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:self.managedObjectContext];
[photo updateWithDictionary:photoJSON];
return photo;
}];
}
Would result in some unnecessary deleting and re-creating of the same data, but would be far simpler than #1.
@implementation Photo
- (void)willSave {
if (!self.tweet) {
[self.managedObjectContext deleteObject:self];
}
}
This is slightly cleaner than manually deleting the photos (in my opinion), but doesn't scale particularly well. It requires me to create the tweet
inverse relationship (with approaches 1-2, the Photo
doesn't actually need to have a reference to the Tweet
).
Additionally, Photo
could be generic and used in contexts other than tweets. Maybe a Photo
can also be associated with a DM, or a user's avatar, in which case I'd need to create inverse relationships for all three and the willSave
implementation will now look like:
@implementation Photo
- (void)willSave {
if (!self.tweet && !self.user && !self.directMessage) {
[self.managedObjectContext deleteObject:self];
}
}
@estromlund Those are good ideas, and I’m honestly not too worried about those orphan records, but being photos they do come with a certain footprint.
#3 is definitely something to consider as a fallback option. I'm afraid #1 won't work with CloudKit, as it sadly doesn't support unique constraints… And #2 sounds like something I should generally start doing, given that CloudKit doesn't support non-optional relationships either 🙄
I wish there was a (clean) way to detect whether
willSave
was triggered by my own code, or CloudKit syncing data in the background. Obviously those things happen on different contexts, but that feels like a brittle foundation to base my logic on.