Skip to main content

Shared Object Channels

Shared Object Channels in ART provide real-time collaborative editing capabilities using CRDT (Conflict-free Replicated Data Types) technology. These channels enable multiple users to simultaneously edit the same data structure with automatic conflict resolution and real-time synchronization.

Unlike regular messaging channels that send discrete messages, shared object channels maintain a synchronized data structure that multiple clients can edit simultaneously. Think of it like Google Docs for any object — changes made by one user are instantly reflected for all other users.

Key Features​

  • CRDT-Based Synchronization: Uses advanced conflict-free replicated data types
  • Real-time Updates: Changes propagate instantly to all connected clients
  • Conflict Resolution: Automatic handling of concurrent edits without data loss
  • RGA Arrays: Replicated Growable Arrays for ordered list operations
  • Echo Suppression: Prevents infinite loops from your own changes

How Shared Object Channels Work​

Shared Object Channels operate through a sophisticated CRDT (Conflict-free Replicated Data Types) system that maintains synchronized state across multiple clients. When you subscribe to a shared object channel, ART creates a live data structure that automatically synchronizes changes between all connected users in real-time.

Subscribing to Shared Object Channels​

Subscribing to a shared object channel uses the same [adk subscribe:completion:] call as any other channel:

[adk subscribe:@"YOUR_SHARED_OBJECT_CHANNEL"
completion:^(BaseSubscription *subscription, NSError *error) {
if (error) {
NSLog(@"Failed to subscribe: %@", error);
return;
}

if ([subscription isKindOfClass:[LiveObjSubscription class]]) {
LiveObjSubscription *live = (LiveObjSubscription *)subscription;
// CRDT-enabled subscription
} else {
// Default or encrypted channel — no CRDT state
}
}];

The system initializes with any existing state from the server and creates a unique replica ID for your client to handle conflict resolution.

Accessing the Shared State​

The core of shared object channels is the state() method, which returns a CRDTProxy object that behaves like a nested NSDictionary , with .set: / .delete / array helpers that are automatically synchronised:

Set a value​

// Get the shared state object
CRDTProxy *sharedState = [live state];
// Now you can work with it using keyed subscripts
[sharedState[@"document"][@"title"] set:@"New Document Title"];

[live flush:^{
// Flushed to server
}];

Delete a key​

// Safe delete (no error if the key doesn't exist)
[sharedState[@"document"][@"title"] delete];
[live flush:^{}];

Every set: / delete is intercepted by the CRDT engine and converted into operations that are synchronized across all connected clients.

Working with Arrays​

Shared object channels provide full array support using RGA (Replicated Growable Array) semantics, which handle concurrent insertions and deletions correctly:

Push​

[sharedState[@"items"] pushItem:@"First item"];
[live flush:^{}];

Pop​

id removed = [sharedState[@"items"] pop];
[live flush:^{}];

Remove at index​

[sharedState[@"items"] removeAtIndex:2];
[live flush:^{}];

Splice (replace a range)​

[sharedState[@"items"] spliceStart:1 deleteCount:1 insertItems:@[@"new item"]];
[live flush:^{}];

The RGA implementation ensures that concurrent array operations from different users are resolved consistently, maintaining the intended order even when users edit simultaneously.

Manual Synchronization Control​

Changes are automatically synchronized, but you can control when pending operations are sent to the server using the flush: method:

// Make multiple changes
[sharedState[@"title"] set:@"New Title"];
[sharedState[@"content"] set:@"Updated content"];
// Manually trigger synchronization —
// all pending changes are batched and sent as a single operation
[live flush:^{
NSLog(@"Changes flushed to server");
}];

By default, the Objective-C ADK batches operations with a 50ms trailing delay to optimize network usage; flush: allows immediate synchronization when needed.

Listening to Updates​

The query: method allows you to observe specific paths of the shared object — useful for optimising updates in large data structures:

Get the current value​

// Query specific path in the object
CRDTQueryHandle *query = [live query:@"user"];

// Get current value without listening continuously
[query executeWithCompletion:^(id initial) {
NSLog(@"Initial: %@", initial);
// `initial` holds the current value at the path 'user'
}];

Listen for changes​

listenWithCallback: fires immediately with the current value, then again on every update at (or under) that path:

void (^dispose)(void) = [query listenWithCallback:^(id data) {

// Handle nil or NSNull (path cleared or doesn't exist)
if (!data || [data isEqual:[NSNull null]]) {
return;
}

// Ensure expected type
if (![data isKindOfClass:[NSDictionary class]]) {
// Unexpected shape — handle defensively
return;
}

NSDictionary *map = (NSDictionary *)data;

// Iterate over entries
for (NSString *key in map) {
NSString *username = key;
id userData = map[key];

// Process user data
NSLog(@"User: %@ Data: %@", username, userData);
}
}];
// Call dispose() later to stop listening and prevent memory leaks
// dispose();