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();