nothirst/TICoreDataSync

TICDSSynchronizationOperation optimization suggestions

Opened this issue · 1 comments

Hi!

I made few optimizations in [TICDSSynchronizationOperation syncChangesAfterCheckingForConflicts:inManagedObjectContext:] and [TICDSSynchronizationOperation backgroundApplicationContextObjectForEntityName: syncIdentifier:] methods and the synchronization become faster few times. I want to share my ideas with you.

So firstly I changed the way how changes are grouping by objectSyncID. In current version changes are grouping by filtering changes array with predicate. It is slowly, because there are a lot of NSString comparisons when we are searching changes with concrete objectSyncID. So I changed it with for loop and NSMapTable where the
keys are objectSyncID and values are arrays of TICDSSyncChange objects.
The code below shows the changes i done.

- (NSArray *)syncChangesAfterCheckingForConflicts:(NSArray *)syncChanges inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
    NSArray *identifiersOfAffectedObjects = [syncChanges valueForKeyPath:@"@distinctUnionOfObjects.objectSyncID"];
        TICDSLog(TICDSLogVerbosityEveryStep, @"Affected Object identifiers: %@", [identifiersOfAffectedObjects componentsJoinedByString:@", "]);

    if (self.localSyncChangesToMergeContext == nil) {
        return syncChanges;
    }

    NSMutableArray *syncChangesToReturn = [NSMutableArray arrayWithCapacity:[syncChanges count]];
    NSMapTable *changedObjectTable = [NSMapTable strongToStrongObjectsMapTable];
    NSString *objSyncIDKey=@"objectSyncID";

    //Groups syncCahnges by objectSyncID
    for (id obj in syncChanges)
    {
        NSString *ticsSyncID = [obj valueForKey:objSyncIDKey];
        NSMutableArray *objectChangesArray = [changedObjectTable objectForKey:ticsSyncID];
        if (!objectChangesArray)
        {
            objectChangesArray = [NSMutableArray arrayWithObject:obj];
            [changedObjectTable setObject:objectChangesArray forKey:ticsSyncID];
        }
        else
        {
            [objectChangesArray addObject:obj];
        }
    }

    //Constructs results array
    NSArray *syncChangesForEachObject = nil;
    for (NSString *eachIdentifier in identifiersOfAffectedObjects)
    {
        syncChangesForEachObject = [changedObjectTable objectForKey:eachIdentifier];
        syncChangesForEachObject = [self remoteSyncChangesForObjectWithIdentifier:eachIdentifier afterCheckingForConflictsInRemoteSyncChanges:syncChangesForEachObject inManagedObjectContext:managedObjectContext];
        [syncChangesToReturn addObjectsFromArray:syncChangesForEachObject];
        [changedObjectTable removeObjectForKey:eachIdentifier];
    }

    [changedObjectTable removeAllObjects];
    changedObjectTable=nil;
    return syncChangesToReturn;
}

The second thing I done is implementing reusability of fetched objects id in [TICDSSynchronizationOperation backgroundApplicationContextObjectForEntityName: syncIdentifier:] method. It gives a great results then we are applying more than one change to the same object in the same TICDSSynchronizationOperation. So I created NSMapTable property in TICDSSynchronizationOperation to cache fetched objects objectID and objectSyncID pairs. If objectSyncID and objectID pair is cached, TICDSSynchronizationOperation takes object directly form managedObjectContext, else executes fetch request to find it and saves results to cache.

- (NSManagedObject *)backgroundApplicationContextObjectForEntityName:(NSString *)entityName syncIdentifier:(NSString *)aSyncIdentifier
{
    id objectID = nil;
    objectID = [self.changedObjectsMapTable objectForKey:aSyncIdentifier];
    NSManagedObject *object = nil;
    // Searches object in cache
    if (objectID && (object = [self.backgroundApplicationContext objectRegisteredForID:objectID]))
    {
        if (object == nil)
        {
            TICDSLog(TICDSLogVerbosityErrorsOnly, @"Error fetching affected object with SyncID: %@", aSyncIdentifier);
        }
    }
    else 
    { // Makes fetch request if there is no cached object with aSyncIdentifier
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:self.backgroundApplicationContext]];

        NSError *anyError = nil;
        NSArray *results = nil;
        if (aSyncIdentifier) {
            [fetchRequest setPredicate:[[self predicateTemplate]predicateWithSubstitutionVariables:@{ @"syncID" : aSyncIdentifier}]];        
            results = [self.backgroundApplicationContext executeFetchRequest:fetchRequest error:&anyError];
        } else {
            results = nil;
        }

        if (results == nil) {
            TICDSLog(TICDSLogVerbosityErrorsOnly, @"Error fetching affected object: %@", anyError);
        }
        else
        {
            object = [results lastObject];
            [self.changedObjectsMapTable setObject:[object objectID] forKey:aSyncIdentifier];
        }
    }
    return object;
}

I'd love to review these changes as a pull request, can you submit one?