2014-10-30 3 views
1

Я работаю над синхронизацией данных между клиентом и сервером. Я использую MagicalRecord (обертка основных данных) для хранения данных на клиенте. У меня есть объект под названием Dirty, который содержит один атрибут, называемый dirty. Это означает, что на клиенте были изменения, которые еще не были перенесены на сервер. dirty получает значение [NSDate date] в любое время, когда атрибут установлен в классе (конечно, при установке dirty задано правильное значение). Каждый другой объект, созданный на клиенте, наследует от Dirty. Идея состоит в том, что мы не будем извлекать новые данные с сервера, пока все клиентские данные не будут оттолкнуты (только для получения новых данных, если все объекты имеют dirty == nil).Использование грязного флага с базовыми данными для синхронизации с сервером

При импорте данных с сервера (с помощью +[NSManagedObject MR_importFromObject:inContext:), атрибут каждого лица dirty получает значение nil (так как клиент уточненный с сервером).

Перед тем, как сэкономлено сбережение (внутри блока сохранения +[MagicalRecord saveWithBlock:completion:]), dirty по-прежнему nil. Однако в блоке завершения выборка объекта, который только что был сохранен (в основном потоке), имеет значение для dirty.

Во время сохранения объект переносится в контекст основного потока. Однако есть проблема, потому что -[NSManagedObject didChangeValueForKey:] вызывается для каждого атрибута, который передается из localContext (фоновый поток) в основной контекст (в основном потоке). dirty устанавливается с [NSDate date] для каждого объекта. Большую часть времени dirty не получает последний набор, что означает, что когда установлен другой атрибут, dirty перезаписывается.

Есть ли способ убедиться, что dirty - последний атрибут, который задается при передаче экземпляра NSManagedObject в контекст основного потока? Я даже хочу установить dirty, когда объект будет сохранен (вместо того, когда будут установлены свойства).

Я пробовал всевозможные варианты, включая проверку против -[NSManagedObject isInserted] и -[NSManagedObject isUpdated] внутри -[NSManagedObject didChangeValueForKey:]. Еще одна вещь, которая вызывает раздражение, заключается в том, что новый объект вставлен перед передачей атрибутов (я думал, что у меня может быть какой-то флаг для блокировки/разблокировки dirty).

Еще одна вещь, которую следует отметить: [NSManagedObject(_NSInternalMethods) _updateFromRefreshSnapshot:includingTransients:] - это то, что вызывается прямо перед тем, как -[NSManagedObject didChangeValueForKey:] вызывается на новый объект.

Любые идеи? Я уже пару дней обманывал это.

ответ

0

После того, как потратил кучу времени на потенциальные решения, я вернулся, чтобы взглянуть на решение этой проблемы самым простым способом. Вот что я придумал:

1) Удаляется -[Dirty didChangeValueForKey:]

2) Создано BTCoreDataService класс, и добавлены следующие методы:

+ (void)saveClientChangesWithSaveBlock:(BTLocalManagedObjectContextBlock)saveBlock 
         completionBlock:(MRSaveCompletionHandler)completionBlock { 
    [self saveAndSetDirty:[NSDate date] 
       saveBlock:saveBlock 
      completionBlock:completionBlock]; 
} 

+ (void)saveServerChangesWithSaveBlock:(BTLocalManagedObjectContextBlock)saveBlock 
         completionBlock:(MRSaveCompletionHandler)completionBlock { 
    [self saveAndSetDirty:nil 
       saveBlock:saveBlock 
      completionBlock:completionBlock]; 
} 

#pragma mark - Internal 

+ (void)saveAndSetDirty:(NSDate *)dirty 
       saveBlock:(BTLocalManagedObjectContextBlock)saveBlock 
     completionBlock:(MRSaveCompletionHandler)completionBlock { 
    [MagicalRecord 
    saveWithBlock:^(NSManagedObjectContext *localContext) { 
     if (saveBlock) { 
      saveBlock(localContext); 

      [[localContext BT_insertedAndUpdatedAtObjectsKindOfClass:[Dirty class]] 
       makeObjectsPerformSelector:@selector(setDirty:) withObject:dirty]; 
     } 
    } 
    completion:completionBlock]; 
} 

А вот реализация на NSManagedObjectContext+BTManagedObjectContext:

- (NSSet *)BT_insertedObjectsKindOfClass:(Class)cls { 
    return 
    [self.insertedObjects 
    filteredSetUsingPredicate:[self BT_isKindOfClassPrediate:cls]]; 
} 

- (NSSet *)BT_updatedObjectsKindOfClass:(Class)cls { 
    return 
    [self.updatedObjects 
    filteredSetUsingPredicate:[self BT_isKindOfClassPrediate:cls]]; 
} 

- (NSSet *)BT_insertedAndUpdatedAtObjectsKindOfClass:(Class)cls { 
    return 
    [[self BT_insertedObjectsKindOfClass:cls] 
    setByAddingObjectsFromSet:[self BT_updatedObjectsKindOfClass:cls]]; 
} 

#pragma mark - Internal 

- (NSPredicate *)BT_isKindOfClassPrediate:(Class)cls { 
    return [NSPredicate predicateWithFormat:@"self isKindOfClass:%@", cls]; 
} 

Единственное, что нужно помнить, чтобы использовать BTCoreDataService для сохранения объектов вместо использования MagicalRecord напрямую.

1

Посмотрите ответ Пола де Ланге в SO 10723861

«TrackedEntity» там будет ваш Dirty сущность и атрибут lastModified бы перевести на ваш атрибут «грязный»

Во время save (срабатывает при наблюдении NSManagedObjectContextWillSaveNotification) метод -objectContextWillSave объединит вставленные объекты и обновленные объекты в набор. Затем он проходит через набор объектов и обновляет атрибут lastModified с меткой времени.

--- Обновление (по запросу.clientUpdatedAt)

Возможно, вы захотите посмотреть at this one, тоже. В нем объясняется, как использовать дополнительные поля для синхронизации. Использование дополнительного атрибута sync_status должно помочь определить, нужно ли загружать объект. Надеюсь, что поможет

+0

Так что я уже делаю это с 'clientUpdatedAt' (и' clientCreatedAt'). 'clientUpdatedAt' не является достаточно хорошим показателем, потому что, когда сервер обновляет объект на клиенте с помощью' updatedAt'time сервера, сущность должна сохранять, а 'clientUpdatedAt' обновляется с' + [NSDate date] 'еще раз. Если я проверю, является ли 'clientUpdatedAt' более поздним, чем' updatedAt', он будет возвращать 'YES' каждый раз. :/ – Jeff

+0

Я обновил свой ответ, вам может потребоваться ввести больше атрибутов для обработки синхронизации. – Olaf

+0

Спасибо за обновление вашего ответа. Задача заключается в следующем: «установите sync_status в 1 на объект модели, когда что-то изменится и его необходимо синхронизировать с сервером». Это та же проблема, что и в моем коде. Я попытался использовать 'didChangeValueForKey:', но это было ненадежным по причинам, упомянутым в моем первоначальном сообщении. Есть ли у вас предложения по выполнению этого шага? Как только это выяснится, все остальное будет плавным. – Jeff

Смежные вопросы