ZDCSyncableObjC 1.0.1

ZDCSyncableObjC 1.0.1

Robbie Hanson 维护。



  • Robbie Hanson

ZDCSyncable (objective-c 版本)

为 Objective-C 中的普通对象提供撤销、重做和合并功能。

(还有一个 Swift 版本,请见 此处。)

由: ZeroDark.cloud: 基于区块链和 AWS 构建的用于您的应用的安全同步和消息框架。

 

撤销与重做

示例 #1

@interface FooBar: ZDCRecord // < Just extend ZDCRecord

// add your properties as usual
@property (nonatomic, copy, readwrite) NSString *someString; 
@property (nonatomic, readwrite) NSUInteger someInt;

@end // That's it !

// And now you get undo & redo support (for free!)

FooBar *foobar = [[FooBar alloc] init];
foobar.someString = @"init";
foobar.someInt = 1;
[foobar clearChangeTracking]; // starting point

foobar.someString = @"modified";
foobar.someInt = 2;

NSDictionary *changeset = [foobar changeset]; // changes since starting point

NSDictionary *redo = [foobar undo:changeset error:nil]; // revert to starting point

// Current state:
// foobar.someString == "init"
// foobar.someInt == 1

[foobar undo:redo error:nil]; // redo == (undo an undo)

// Current state:
// foobar.someString == "modified"
// foobar.someInt == 2

通过容器类支持复杂对象

  • ZDCDictionary
  • ZDCOrderedDictionary
  • ZDCSet
  • ZDCOrderedSet
  • ZDCArray

示例 #2

@interface FooBuzz: ZDCRecord // < Just extend ZDCRecord

// add your properties as usual
@property (nonatomic, readwrite) NSUInteger someInt;

// or use smart containers!
@property (nonatomic, readonly) ZDCDictionary *dict;

@end

FooBuzz *foobuzz = [[FooBuzz alloc] init];
foobuzz.someInt = 1;
foobuzz.dict[@"foo"] = @"buzz";
[foobuzz clearChangeTracking]; // starting point

foobuzz.someInt = 2;
foobuzz.dict[@"foo"] = @"modified";

NSDictionary *undo = [foobuzz changeset]; // changes since starting point

NSDictionary *redo = [foobuzz undo:undo error:nil]; // revert to starting point
  
// Current state:
// foobuzz.someInt == 1
// foobuzz.dict["foo"] == "buzz"

[foobuzz undo:redo error:nil]; // redo == (undo an undo)

// Current state:
// foobuzz.someInt == 2
// foobuzz.dict["foo"] == "modified"

 

合并

您还可以合并更改!(例如,从云端)

FooBuzz *local = [[FooBuzz alloc] init];
local.someInt = 1;
local.dict[@"foo"] = @"buzz";
[local clearChangeTracking]; // starting point

FooBuzz *cloud = [local copy];
NSMutableArray<NSDictionary*> *changesets = [NSMutableArray array];
	
// local modifications
	
local.someInt = 2;
local.dict[@"foo"] = @"modified";

[changesets addObject:[local changeset]];
// ^ pending local changes (not yet pushed to cloud)

// cloud modifications
			
cloud.dict[@"duck"]  = @"quack";
		
// Now merge cloud version into local.
// Automatically take into account our pending local changes.

[local mergeCloudVersion:cloud withPendingChangesets:changesets error:nil];
	
// Merged state:
// local.someInt == 2
// local.dict["foo"] == "modified"
// local.dict["duck"] == "quack"

 

动机

与云端同步数据需要能够正确合并更改。而正确合并更改则需要了解更改内容。

这在教程中通常被一带而过,因此人们往往会忘记它...直到实际编写代码的时候。然后一切都会变得混乱不堪!

与云端同步对象意味着了解如何从多个设备合并更改。这比预期要难,因为默认情况下,这是您唯一要合并的信息

  1. 对象当前版本,如您在云端所见
  2. 当前对象的版本,正如它在您的数据库中一样

但是缺少了一些东西。如果两个版本之间的属性 someInt 不同,那么这可能意味着

  • 仅由远程设备更改
  • 仅由本地设备更改
  • 双方设备都进行了更改

为了正确执行合并,您需要知道这个问题的答案。

缺少的是对本地对象所做的更改列表。也就是说,已经进行的更改,但尚未推送到云端的更改。有了这些信息,我们可以进行适当的合并。因为现在我们知道了

  1. 对象当前版本,如您在云端所见
  2. 当前对象的版本,正如它在您的数据库中一样
  3. 对本地对象所做的更改列表,包括更改的键和它们的原始值

因此,如果您要正确合并更改,您需要跟踪这些信息。您可以通过困难的方式(手动)或简单的方式(使用为您自动提供跟踪的基类)来完成此操作。无论如何,您仍然没有摆脱困境!

跟踪简单记录的更改稍微有些简单。也就是说,只有一个少量键/值对的对象。并且所有值都是原生的(数字、布尔值、字符串)。但是当您的应用程序变得更加高级,您需要更复杂的对象时怎么办呢?

如果您的某个属性是数组呢?或者字典?或者集合?

说实话,编写这些东西并不困难。这不是rocket science。但这确实需要大量的单元测试来正确处理所有的小边界情况。这意味着您可以自己编写这些单元测试,或者您可以使用已经由社区击败测试的开源版本。然后花更多的时间让你的应用程序变得出色。

 

入门

ZDCSyncableObjcC 通过 CocoaPods 提供。

CocoaPods

请在 Podfile 中添加以下内容

pod 'ZDCSyncableObjC'

然后像平常一样运行 pod install。然后您可以通过

// using module-style imports:
@import ZDCSyncableObjC;

// or you can use classic-style imports:
#import <ZDCSyncableObjC/ZDCSyncableObjC.h>

 

额外功能:不可变性

所有 ZDCObject 子类都可以变得不可变。这包括所有容器类(例如 ZDCDictionary),以及您创建的任何 ZDCRecord 的自定义子类。

myCustomSwiftObject.makeImmutable() // Boom! Cannot be modified now!

这一概念类似于拥有独立的 NSDictionary/NSMutableDictionary 类。但这项技术使用起来更为简便。

(如果您想知道它是如何工作的:更改跟踪已经可以监控对象的更改。因此,一旦将对象标记为不可变,修改对象的尝试将抛出异常。如果您想进行修改,只需复制对象,然后修改副本即可。)