Moim rozwiązaniem było przechowywanie dwóch kopii zestawu danych w bazie danych CoreData. Jeden reprezentuje ostatni znany stan serwera i jest niezmienny. Drugi jest edytowany przez użytkownika.
Gdy nadszedł czas na synchronizację zmian, aplikacja tworzy różnicę między edytowanymi i niezmiennymi kopiami danych. Aplikacja przesyła diff do usługi internetowej, która stosuje różnicę do własnej kopii danych. Odpowiada on pełnej kopii zbioru danych, który aplikacja zastępuje na obu kopiach danych.
Zaletami są:
- Jeśli nie ma połączenia z siecią, żadne zmiany nie zostaną utracone: diff jest obliczany za każdym razem, gdy zestaw danych musi zostać wysłane i niezmienny kopia zostanie zmieniony tylko na sukces synchronizacja.
- Wysyłana jest tylko minimalna ilość informacji, które należy wysłać.
- Wiele osób może edytować te same dane w tym samym czasie bez użycia strategii blokowania z minimalną możliwością utraty danych przez nadpisanie.
Wadami są:
- pisanie kodu diffing jest złożona.
- Pisanie usługi łączenia jest złożone.
- Jeśli nie jesteś guru metaprogramowania, przekonasz się, że kod diff/merge jest kruchy i musi się zmieniać po każdej zmianie modelu obiektu.
Oto niektóre kwestie miałem podczas wymyślanie strategii:
- Jeśli pozwolisz wprowadzenie zmian w trybie offline, zamek zameldowania/wymeldowania nie zadziała (w jaki sposób można ustanowić zablokować bez połączenia?).
- Co się stanie, jeśli dwie osoby edytują te same dane w tym samym czasie?
- Co się dzieje, gdy jedna osoba edytuje dane na jednym urządzeniu z systemem iOS, gdy jest bezpołączona, wyłącza je, edytuje na innym urządzeniu, a następnie ponownie włącza oryginalne urządzenie?
- Wielowątkowość z CoreData to sama klasa problemów.
Najbliższy rzeczą Słyszałem o wsparciu out-of-the-box nic robić zdalnie jak to jest nowy system synchronizacji iCloud/CoreData w iOS6, który automatycznie przekazuje podmiotom z bazy CoreData do iCloud kiedy się zmieniają. Oznacza to jednak, że musisz używać iCloud.
EDYCJA: Jest to bardzo późno, wiem, ale tutaj jest klasa, która jest zdolna do tworzenia diff między dwie instancje NSManagedObject.
// SZManagedObjectDiff.h
@interface SZManagedObjectDiff
- (NSDictionary *)diffNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject
@end
// SZManagedObjectDiff.m
#import "SZManagedObjectDiff.h"
@implementation SZManagedObjectDiff
- (NSDictionary *)diffNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSDictionary *attributeDiff = [self diffAttributesOfNewObject:newObject withOldObject:oldObject];
NSDictionary *relationshipsDiff = [self diffRelationshipsOfNewObject:newObject withOldObject:oldObject];
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
if (attributeDiff.count > 0) {
diff[@"attributes"] = attributeDiff;
}
if (relationshipsDiff.count > 0) {
diff[@"relationships"] = relationshipsDiff;
}
if (diff.count > 0) {
diff[@"entityName"] = newObject ? newObject.entity.name : oldObject.entity.name;
NSString *idAttributeName = newObject ? newObject.entity.userInfo[@"id"] : oldObject.entity.userInfo[@"id"];
if (idAttributeName) {
id itemId = newObject ? [newObject valueForKey:idAttributeName] : [oldObject valueForKey:idAttributeName];
if (itemId) {
diff[idAttributeName] = itemId;
}
}
}
return diff;
}
- (NSDictionary *)diffRelationshipsOfNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
NSDictionary *relationships = newObject == nil ? [[oldObject entity] relationshipsByName] : [[newObject entity] relationshipsByName];
for (NSString *name in relationships) {
NSRelationshipDescription *relationship = relationships[name];
if (relationship.deleteRule != NSCascadeDeleteRule) continue;
SEL selector = NSSelectorFromString(name);
id newValue = nil;
id oldValue = nil;
if (newObject != nil && [newObject respondsToSelector:selector]) newValue = [newObject performSelector:selector];
if (oldObject != nil && [oldObject respondsToSelector:selector]) oldValue = [oldObject performSelector:selector];
if (relationship.isToMany) {
NSArray *changes = [self diffNewSet:newValue withOldSet:oldValue];
if (changes.count > 0) {
diff[name] = changes;
}
} else {
NSDictionary *relationshipDiff = [self diffNewObject:newValue withOldObject:oldValue];
if (relationshipDiff.count > 0) {
diff[name] = relationshipDiff;
}
}
}
return diff;
}
- (NSDictionary *)diffAttributesOfNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
NSArray *attributeNames = newObject == nil ? [[[oldObject entity] attributesByName] allKeys] : [[[newObject entity] attributesByName] allKeys];
for (NSString *name in attributeNames) {
SEL selector = NSSelectorFromString(name);
id newValue = nil;
id oldValue = nil;
if (newObject != nil && [newObject respondsToSelector:selector]) newValue = [newObject performSelector:selector];
if (oldObject != nil && [oldObject respondsToSelector:selector]) oldValue = [oldObject performSelector:selector];
newValue = newValue ? newValue : [NSNull null];
oldValue = oldValue ? oldValue : [NSNull null];
if (![newValue isEqual:oldValue]) {
diff[name] = @{ @"new": newValue, @"old": oldValue };
}
}
return diff;
}
- (NSArray *)diffNewSet:(NSSet *)newSet withOldSet:(NSSet *)oldSet {
NSMutableArray *changes = [NSMutableArray array];
// Find all items that have been newly created or updated.
for (NSManagedObject *newItem in newSet) {
NSString *idAttributeName = newItem.entity.userInfo[@"id"];
NSAssert(idAttributeName, @"Entities must have an id property set in their user info.");
id newItemId = [newItem valueForKey:idAttributeName];
NSManagedObject *oldItem = nil;
for (NSManagedObject *setItem in oldSet) {
id setItemId = [setItem valueForKey:idAttributeName];
if ([setItemId isEqual:newItemId]) {
oldItem = setItem;
break;
}
}
NSDictionary *diff = [self diffNewObject:newItem withOldObject:oldItem];
if (diff.count > 0) {
[changes addObject:diff];
}
}
// Find all items that have been deleted.
for (NSManagedObject *oldItem in oldSet) {
NSString *idAttributeName = oldItem.entity.userInfo[@"id"];
NSAssert(idAttributeName, @"Entities must have an id property set in their user info.");
id oldItemId = [oldItem valueForKey:idAttributeName];
NSManagedObject *newItem = nil;
for (NSManagedObject *setItem in newSet) {
id setItemId = [setItem valueForKey:idAttributeName];
if ([setItemId isEqual:oldItemId]) {
newItem = setItem;
break;
}
}
if (!newItem) {
NSDictionary *diff = [self diffNewObject:newItem withOldObject:oldItem];
if (diff.count > 0) {
[changes addObject:diff];
}
}
}
return changes;
}
@end
Jest więcej informacji o tym, co robi, jak to robi i swoje ograniczenia/założenia tutaj:
http://simianzombie.com/?p=2379
Myślę, że funkcja synchronizacji, o której mówisz jest dostępna w IOS5, ale show-stopper (jak wspomniano) jest to, że jest to synchronizacja z iCloud, a nie z systemem, z którym muszę się połączyć (SQLServer). Bardzo podoba mi się twój pomysł, ale jestem zaniepokojony problemami, które wskazałeś: ustalenie delt. Czy masz jakieś doświadczenie z NSIncrementalStore? Nadal jestem zdezorientowany co to jest. – JustLearningAgain
NSIncrementalStore jest klasą podstawową do implementacji dowolnego systemu pamięci masowej jako sklepu CoreData. Chcesz użyć pliku XML z interfejsem API CoreData? Odziedzicz z NSIncrementalStore i napisz metody, aby to zrobić. Nie sądzę, że to pomoże w twojej sytuacji. – Ant
Czy pozwala mi to robić jedno i drugie? Czy mogę użyć go do aktualizacji podstawowych danych, a także mojej zewnętrznej usługi internetowej? – JustLearningAgain