Getting started: LiveObjects in Swift
This guide shows how to integrate Ably LiveObjects into your Swift application.
You will learn how to:
- Create an Ably account and get an API key for authentication.
- Install the Ably Pub/Sub SDK.
- Create a channel with LiveObjects functionality enabled.
- Use the PathObject API to access objects on a channel.
- Create, update and subscribe to changes on LiveObjects data structures: LiveMap and LiveCounter.
Authentication
An API key is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using basic authentication, or to generate tokens for untrusted clients using token authentication.
Sign up for a free account and create your own API key in the dashboard or use the Control API to create an API key programmatically.
API keys and tokens have a set of capabilities assigned to them that specify which operations can be performed on which resources. The following capabilities are available for LiveObjects:
object-subscribe- grants clients read access to LiveObjects, allowing them to get the channel object and subscribe to updates.object-publish- grants clients write access to LiveObjects, allowing them to perform mutation operations on objects.
To use LiveObjects, an API key must have at least the object-subscribe capability. With only this capability, clients will have read-only access, preventing them from calling mutation methods on LiveObjects.
For the purposes of this guide, make sure your API key includes both object-subscribe and object-publish capabilities to allow full read and write access.
Install Ably Pub/Sub SDK
LiveObjects is available as part of the Ably Pub/Sub SDK via the dedicated LiveObjects plugin. The plugin ships as the AblyLiveObjects product of the ably-cocoa package from version 1.3.0 onwards, and is available through Swift Package Manager only. There is no separate package or version to install: the plugin is versioned and released as part of ably-cocoa.
The LiveObjects plugin requires a minimum deployment target of iOS 14, macOS 11, or tvOS 14, and Xcode 16.3 or later.
Add the Ably SDK and the LiveObjects plugin to your Xcode project:
- Select File → Add Package Dependencies.
- Paste
https://github.com/ably/ably-cocoain the search box. - Set the dependency rule to version 1.3.0 or later.
- Add both the
AblyandAblyLiveObjectsproducts to your target.
To install the plugin in another Swift package instead, add both products to your target's dependencies in Package.swift:
1
2
3
4
5
6
7
8
9
10
11
12
dependencies: [
.package(url: "https://github.com/ably/ably-cocoa", from: "1.3.0"),
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "Ably", package: "ably-cocoa"),
.product(name: "AblyLiveObjects", package: "ably-cocoa"),
]
),
]For the complete installation notes, platform requirements, usage overview and example app, see the LiveObjects section of the ably-cocoa README.
Import the SDK and the LiveObjects plugin into your project:
1
2
import Ably
import AblyLiveObjectsInstantiate a client
Instantiate an Ably Realtime client from the Pub/Sub SDK, providing the LiveObjects plugin:
1
2
3
4
let clientOptions = ARTClientOptions(key: "demokey:*****")
clientOptions.plugins = [.liveObjects: AblyLiveObjects.Plugin.self]
let realtime = ARTRealtime(options: clientOptions)A ClientOptions object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key and register the LiveObjects plugin through clientOptions.plugins so that the client can use LiveObjects functionality.
Create a channel
LiveObjects is managed and persisted on channels. To use LiveObjects, you must first create a channel with the correct channel mode flags:
OBJECT_SUBSCRIBE- required to access objects on a channel.OBJECT_PUBLISH- required to create and modify objects on a channel.
1
2
3
let channelOptions = ARTRealtimeChannelOptions()
channelOptions.modes = [.objectPublish, .objectSubscribe]
let channel = realtime.channels.get("test-channel", options: channelOptions)Get the channel object
The channel.object property gives access to the LiveObjects API for a channel.
Use channel.object.get() to obtain the channel object. The channel object is a LiveMap that always exists on a channel and acts as the top-level entry point for accessing and persisting objects. It is returned as a LiveMapPathObject, which provides a path-based API for accessing and manipulating the object hierarchy.
channel.object.get() is an async function that suspends with try await until the LiveObjects state is synchronized with the Ably system. Reads are synchronous and local:
1
let rootObject = try await channel.object.get()Create and assign objects
You can create and assign objects using the LiveMap.create() and LiveCounter.create() static methods. These methods return a blueprint describing the object to create; the actual object is created when you assign it to a key with set(). Wrap a blueprint in .liveCounter(...) or .liveMap(...) when passing it to set(), while primitive values such as strings and numbers can be passed directly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Create a LiveCounter with initial value 0
let visits = LiveCounter.create(initialCount: 0)
// Assign it to the 'visits' key on the channel object
try await rootObject.set(key: "visits", value: .liveCounter(visits))
// Create a LiveMap with initial entries
let reactions = LiveMap.create(entries: [
"likes": 0,
"hearts": 0,
])
// Assign it to the 'reactions' key on the channel object
try await rootObject.set(key: "reactions", value: .liveMap(reactions))
// Infer types for the assigned objects
let visitsCounter = rootObject.get(key: "visits").asLiveCounter()
let reactionsMap = rootObject.get(key: "reactions").asLiveMap()rootObject.get(key:) returns a general PathObject that doesn't yet know the type of the value it points to. Call one of the as* methods, such as asLiveCounter() or asLiveMap(), to work with the value as a specific type. These casts are always safe to call: they never throw, even if the value at the path has a different type. Learn more about Type inference.
Subscribe to updates
Subscribe to realtime updates using the subscribe() method on a PathObject. You will be notified when the object at that path is updated by other clients or by you:
1
2
3
4
5
6
7
8
9
10
11
// Subscribe to counter updates
let counterSubscription = try visitsCounter.subscribe { event in
guard let value = try? event.object.asLiveCounter().value() else { return }
print("Visits counter updated: \(value)")
}
// Subscribe to map updates
let mapSubscription = try reactionsMap.subscribe { event in
guard let json = try? event.object.compactJson() else { return }
print("Reactions updated: \(json)")
}The subscription callback receives an event that exposes:
object: APathObjectpointing to the path where the change occurred.message: TheObjectMessagethat carried the operation that led to the change (nullable).
The subscribe() method also returns a Subscription object. When you no longer want to receive updates, call unsubscribe() on it:
1
2
3
4
5
6
7
8
// Keep the Subscription returned by subscribe()
let subscription = try visitsCounter.subscribe { event in
guard let value = try? event.object.asLiveCounter().value() else { return }
print("Visits counter updated: \(value)")
}
// Later, stop receiving updates
subscription.unsubscribe()Every PathObject also exposes an events() AsyncStream, so you can consume updates with for await, which is often the more idiomatic pattern in Swift. Breaking out of the loop, or cancelling the enclosing task, automatically unsubscribes:
1
2
3
4
for await event in try visitsCounter.events() {
guard let value = try? event.object.asLiveCounter().value() else { continue }
print("Visits counter updated: \(value)")
}Update objects
Update objects using mutation methods on PathObject. All subscribers (including you) will be notified of the changes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Update counter
try await visitsCounter.increment(amount: 5)
// console: Visits counter updated: 5.0
try await visitsCounter.decrement(amount: 2)
// console: Visits counter updated: 3.0
// Update map
try await reactionsMap.set(key: "likes", value: 10)
// console: Reactions updated: {"likes":10.0,"hearts":0.0}
try await reactionsMap.set(key: "hearts", value: 5)
// console: Reactions updated: {"likes":10.0,"hearts":5.0}
try await reactionsMap.remove(key: "likes")
// console: Reactions updated: {"hearts":5.0}Next steps
This quickstart introduced the basic concepts of LiveObjects and demonstrated how the path-based API works. The next steps are to:
- Learn about the PathObject and Instance APIs.
- Read more about LiveCounter and LiveMap.
- Learn about Objects Lifecycle Events.
- Learn about Type inference for your LiveObjects.