0

I'm working on an old incredibly poorly written Objective-C app. The feature I'm working on involves taking different record types from core-data and sending them to a server. And one record type (A) has to be sent before another one (B) can be sent.

EDIT: A refers to ActivitySession objects and B refers to ActivityRun objects.

Here's the problem: somewhere in the app, there's some kind of listener deleting all of the B records after the A records are sent. I've spent a while trying to figure out where this is happening, but I've had no luck. My solution was to then just load all of the B records into memory before sending the A records (thinking even if the B records are deleted from core data, they will still exist in memory). But I'm finding after the B records are deleted from core data, my array of records are also being cleared.

Here is the meat of the logic I'm talking about:

__block NSArray* activityRuns = [coreData fetchByEntitiyName:@"ActivityRun"];

NSLog(@"---> Sending %@ students", @([students count]));
return [self sendStudents:students withApiKey:apiKey]
.then(^{
  // This must happen before sending anything with activity references
  NSLog(@"---> Fetching activities");
  return [self fetchActivitiesWithApiKey:apiKey];
})
.then(^{
  NSArray* data = [coreData fetchByEntitiyName:@"ActivitySession"];
  NSLog(@"---> Sending %@ activity sessions", @([data count]));
  return [self sendActivitySessions:data withApiKey:apiKey].catch(failLogger(@"Failed to upload activity sessions", nil));
})
.then(^{
  NSLog(@"---> Sending %@ activity runs", @([activityRuns count]));
  return [self sendActivityRuns:activityRuns withApiKey:apiKey].catch(failLogger(@"Failed to upload activity runs", nil));
})

activityRuns is the in-memory data I'm trying to send (with [self sendActivityRuns:activityRuns withApiKey:apiKey]). Here is how fetchByEntitiyName is defined:

- (NSArray*) fetchByEntitiyName:(NSString*) entityName {
    NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] initWithEntityName:entityName];
    NSError* error = nil;
    NSArray* results = [_mainQueueContext executeFetchRequest:fetchRequest error:&error];
    if (results == nil) {
            ELog(@"--- Failed to fetch %@: %@", entityName, error);
            return @[];
    }
    return results;
}

activityRuns isn't used/passed anywhere other than in the code snippet above where each activity run is sent using an HTTP request. I tried adding breakpoints to right after the array is created (the objects are valid and contain data) and right before they're sent (they're all nil).

How do I keep core data from shitting on my in-memory objects when something deletes records?

SimpleJ
  • 13,812
  • 13
  • 53
  • 93
  • Please add the log output. The "A"'s and "B"'s in the first paragraph of the question make it hard to understand. Please replace "A" and "B" with the entity names that appear in the code. – danh May 11 '18 at 22:39

2 Answers2

1

There are a few ways for this to happen:

  • The obvious one are calls to NSManagedObjectContext.deleteObject, just look for deleteObject and put breakpoints there.
  • Another one is if A and B have a relation with cascade delete, and you delete the parent, then B is deleted. For example if a Student has a list of ActivityRun-s in a property like: student.activityRuns (array of child objects), or a reverse property - activityRun.student (single parent object), and in your CoreData model this property is set as cascade delete then if you delete a student, all activityRuns will be gone. If you use "on delete set null" instead of "on delete cascade", the children won't be deleted automatically, but you'll have to delete them manually when needed. Another way is to "detach" a child from the parent by setting activityRun.student = nil;, it should have the same effect (again have to delete manually when needed).
  • Another option is if you do raw SQL DELETE queries against this database

There's a way to see all the CoreData SQL commands that happen under the hood - see here.

battlmonstr
  • 5,841
  • 1
  • 23
  • 33
  • Not sure why I didn't think to put breakpoints on `deleteObject` calls. Thanks for the response and putting up with my salty ill-defined question. – SimpleJ May 14 '18 at 16:33
1

Simple solution:

Why don't you prepare all the data you need to send to the server first, including format and serialisation? You can then store this in memory or elsewhere and send to your server in the right order.

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Unfortunately there's already loads of request code written, and the client doesn't want to spend the time/money rewriting it for this one-off feature. If it were up to me, all of this data would be sent in a single request instead of hundreds. – SimpleJ May 14 '18 at 16:31