diff --git a/src/data/languages/languageData.ts b/src/data/languages/languageData.ts index eee39146d0..2826da81ef 100644 --- a/src/data/languages/languageData.ts +++ b/src/data/languages/languageData.ts @@ -52,7 +52,7 @@ export default { }, liveObjects: { javascript: '2.21', - swift: '0.4', + swift: '1.2', java: '1.8', }, liveSync: { diff --git a/src/data/nav/liveobjects.ts b/src/data/nav/liveobjects.ts index aed0809e2e..ec37922c66 100644 --- a/src/data/nav/liveobjects.ts +++ b/src/data/nav/liveobjects.ts @@ -125,8 +125,9 @@ export default { external: true, }, { - link: 'https://sdk.ably.com/builds/ably/ably-liveobjects-swift-plugin/main/AblyLiveObjects/documentation/ablyliveobjects/', - name: 'Swift plugin', + // TODO: verify this URL resolves once the ably-cocoa-hosted AblyLiveObjects DocC is published + link: 'https://sdk.ably.com/builds/ably/ably-cocoa/main/AblyLiveObjects/documentation/ablyliveobjects/', + name: 'Swift SDK', external: true, }, { diff --git a/src/pages/docs/api/realtime-sdk/channels.mdx b/src/pages/docs/api/realtime-sdk/channels.mdx index 06c2978224..79bcea4487 100644 --- a/src/pages/docs/api/realtime-sdk/channels.mdx +++ b/src/pages/docs/api/realtime-sdk/channels.mdx @@ -103,18 +103,15 @@ Provides access to the [`RealtimeAnnotations`](#realtime-annotations) object for - + #### object A public field providing access to the [RealtimeObject](/docs/liveobjects) for this channel, which can be used to read, modify and subscribe to LiveObjects on a channel. - - -#### objects - -Provides access to the [Objects](/docs/liveobjects) object for this channel which can be used to read, modify and subscribe to LiveObjects on a channel. +An extension property provided by the AblyLiveObjects plugin, giving access to the [RealtimeObject](/docs/liveobjects) for this channel, which can be used to read, modify and subscribe to LiveObjects on a channel. + ### Channel Methods diff --git a/src/pages/docs/liveobjects/batch.mdx b/src/pages/docs/liveobjects/batch.mdx index 4de143c36b..e0b0b72ee7 100644 --- a/src/pages/docs/liveobjects/batch.mdx +++ b/src/pages/docs/liveobjects/batch.mdx @@ -25,7 +25,7 @@ meta_description: "Group multiple objects operations into a single channel messa diff --git a/src/pages/docs/liveobjects/concepts/instance.mdx b/src/pages/docs/liveobjects/concepts/instance.mdx index 15727973b7..819f27d540 100644 --- a/src/pages/docs/liveobjects/concepts/instance.mdx +++ b/src/pages/docs/liveobjects/concepts/instance.mdx @@ -31,9 +31,7 @@ An `Instance` can also wrap a primitive value, for example when obtained from a @@ -52,6 +50,18 @@ console.log(visits?.id); // e.g. counter:abc123@1234567890 console.log(visits?.value()); // e.g. 5 ``` +```swift +// Get a PathObject for the channel object +let rootObject = try await channel.object.get() + +// Get the specific Instance of a LiveCounter located at the 'visits' key +let visitsInstance = try rootObject.get(key: "visits").instance() // nil only if nothing exists at the path +if case .liveCounter(let visits)? = visitsInstance { + print(visits.id) // e.g. counter:abc123@1234567890 + print(try visits.value) // e.g. 5.0 +} +``` + ```java // Get a PathObject for the channel object LiveMapPathObject rootObject = channel.object.get().join(); @@ -69,6 +79,9 @@ if (visitsInstance != null) { The `instance()` method returns `undefined` only if nothing exists at that path. It wraps whatever value resolves there: a `LiveMap`, a `LiveCounter`, or a primitive value. A primitive-backed `Instance` is read-only: it has no `id` and exposes the primitive via `value()`. + +The `instance()` method returns `nil` only if nothing exists at that path. It wraps whatever value resolves there, surfaced as the corresponding case of the `Instance` enum: a `LiveMap` becomes `.liveMap`, a `LiveCounter` becomes `.liveCounter`, and a primitive value becomes `.primitive`. A primitive-backed `Instance` (`.primitive`) is read-only: it has no `id` and exposes the value through its `value` property. + The `instance()` method returns `null` only if nothing exists at that path. It wraps whatever value resolves there: a `LiveMap` or `LiveCounter` becomes a `LiveMapInstance` or `LiveCounterInstance`, and a primitive value becomes a read-only primitive instance (for example `StringInstance`). @@ -84,6 +97,16 @@ const visits = rootObject.get('visits').instance(); await visits?.increment(5); ``` +```swift +// Obtain an Instance for a LiveCounter +let visitsInstance = try rootObject.get(key: "visits").instance() + +// Increment the specific LiveCounter instance +if case .liveCounter(let visits)? = visitsInstance { + try await visits.increment(amount: 5) +} +``` + ```java // Obtain an Instance for a LiveCounter Instance visitsInstance = rootObject.get("visits").instance(); @@ -153,6 +176,51 @@ if (visitsInstance != null && visitsInstance.getType() == ValueType.LIVE_COUNTER See the [Type inference documentation](/docs/liveobjects/typing?lang=java#type-inference) for the full contract, including how the path-layer casts differ. + +## Type inference + +`Instance` is an enum with three cases: `.liveMap`, `.liveCounter` and `.primitive`. There are no casts on an `Instance`; discriminate between the cases with an exhaustive `switch`, which the compiler checks. A type mismatch error (code 92007, which the path layer throws for wrong-typed writes) cannot occur from this discrimination. In exchange, reads on a typed instance never return `nil` for a type mismatch: `try map.size` and `try counter.value` are non-optional. + + +```swift +switch try rootObject.get(key: "visits").instance() { +case .liveCounter(let visits): + try await visits.increment(amount: 1) +case .liveMap(let map): + print(try map.size) +case .primitive(let primitive): + print(try primitive.value) +case .none: + print("nothing exists at this path") +} +``` + + +Use `if case` when you only need to handle one type: + + +```swift +if case .liveCounter(let visits)? = try rootObject.get(key: "visits").instance() { + try await visits.increment(amount: 1) +} +``` + + +When you only need to inspect the type, read the `type` property on the `Instance`, which is non-optional and runs in constant time: + + +```swift +if let instance = try rootObject.get(key: "visits").instance() { + print(instance.type) // e.g. liveCounter +} +``` + + +Reads on an instance throw an `ARTErrorInfo` only when LiveObjects cannot be accessed at all: the channel is in the `DETACHED` or `FAILED` state (error code 90001), or the channel is missing the `object_subscribe` mode (error code 40024). They never throw for a type mismatch or an absent entry. + +See the [Type inference documentation](/docs/liveobjects/typing?lang=swift#type-inference) for the full contract, including how the path-layer casts differ. + + ## Navigate an Instance For `LiveMap` instances, use the `get(key)` method to navigate to a child value: @@ -162,6 +230,11 @@ For `LiveMap` instances, use the `get(key)` method to navigate to a child value: Unlike `PathObject`, the `get(key)` method on an `Instance` can return `undefined` if the entry doesn't exist, if the object at that location has been deleted, or if the current instance is not a `LiveMap`. + + + + + + + + + + + +