Skip to content

Commit 224c87b

Browse files
Merge pull request #20 from ably/groundwork-for-object-sync
Groundwork for object sync
2 parents a668fa1 + 51f82dc commit 224c87b

7 files changed

Lines changed: 151 additions & 61 deletions

File tree

Sources/AblyLiveObjects/DefaultRealtimeObjects.swift

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ import Ably
22
internal import AblyPlugin
33

44
/// The class that provides the public API for interacting with LiveObjects, via the ``ARTRealtimeChannel/objects`` property.
5-
internal class DefaultRealtimeObjects: RealtimeObjects {
6-
private weak var channel: ARTRealtimeChannel?
5+
internal final class DefaultRealtimeObjects: RealtimeObjects {
6+
// Used for synchronizing access to all of this instance's mutable state. This is a temporary solution just to allow us to implement `Sendable`, and we'll revisit it in https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/3.
7+
private let mutex = NSLock()
8+
9+
private let coreSDK: CoreSDK
710
private let logger: AblyPlugin.Logger
8-
private let pluginAPI: AblyPlugin.PluginAPIProtocol
911

1012
// These drive the testsOnly_* properties that expose the received ProtocolMessages to the test suite.
1113
private let receivedObjectProtocolMessages: AsyncStream<[InboundObjectMessage]>
1214
private let receivedObjectProtocolMessagesContinuation: AsyncStream<[InboundObjectMessage]>.Continuation
1315
private let receivedObjectSyncProtocolMessages: AsyncStream<[InboundObjectMessage]>
1416
private let receivedObjectSyncProtocolMessagesContinuation: AsyncStream<[InboundObjectMessage]>.Continuation
1517

16-
internal init(channel: ARTRealtimeChannel, logger: AblyPlugin.Logger, pluginAPI: AblyPlugin.PluginAPIProtocol) {
17-
self.channel = channel
18+
internal init(coreSDK: CoreSDK, logger: AblyPlugin.Logger) {
19+
self.coreSDK = coreSDK
1820
self.logger = logger
19-
self.pluginAPI = pluginAPI
2021
(receivedObjectProtocolMessages, receivedObjectProtocolMessagesContinuation) = AsyncStream.makeStream()
2122
(receivedObjectSyncProtocolMessages, receivedObjectSyncProtocolMessagesContinuation) = AsyncStream.makeStream()
2223
}
@@ -27,23 +28,23 @@ internal class DefaultRealtimeObjects: RealtimeObjects {
2728
notYetImplemented()
2829
}
2930

30-
internal func createMap(entries _: any LiveMap) async throws(ARTErrorInfo) -> any LiveMap {
31+
internal func createMap(entries _: [String: LiveMapValue]) async throws(ARTErrorInfo) -> any LiveMap {
3132
notYetImplemented()
3233
}
3334

3435
internal func createMap() async throws(ARTErrorInfo) -> any LiveMap {
3536
notYetImplemented()
3637
}
3738

38-
internal func createCounter(count _: Int) async throws(ARTErrorInfo) -> any LiveCounter {
39+
internal func createCounter(count _: Double) async throws(ARTErrorInfo) -> any LiveCounter {
3940
notYetImplemented()
4041
}
4142

4243
internal func createCounter() async throws(ARTErrorInfo) -> any LiveCounter {
4344
notYetImplemented()
4445
}
4546

46-
internal func batch(callback _: (any BatchContext) -> Void) async throws {
47+
internal func batch(callback _: sending (sending any BatchContext) -> Void) async throws {
4748
notYetImplemented()
4849
}
4950

@@ -57,9 +58,17 @@ internal class DefaultRealtimeObjects: RealtimeObjects {
5758

5859
// MARK: Handling channel events
5960

60-
internal private(set) var testsOnly_onChannelAttachedHasObjects: Bool?
61+
private nonisolated(unsafe) var onChannelAttachedHasObjects: Bool?
62+
internal var testsOnly_onChannelAttachedHasObjects: Bool? {
63+
mutex.withLock {
64+
onChannelAttachedHasObjects
65+
}
66+
}
67+
6168
internal func onChannelAttached(hasObjects: Bool) {
62-
testsOnly_onChannelAttachedHasObjects = hasObjects
69+
mutex.withLock {
70+
onChannelAttachedHasObjects = hasObjects
71+
}
6372
}
6473

6574
internal var testsOnly_receivedObjectProtocolMessages: AsyncStream<[InboundObjectMessage]> {
@@ -74,22 +83,14 @@ internal class DefaultRealtimeObjects: RealtimeObjects {
7483
receivedObjectSyncProtocolMessages
7584
}
7685

77-
internal func handleObjectSyncProtocolMessage(objectMessages: [InboundObjectMessage], protocolMessageChannelSerial _: String) {
86+
internal func handleObjectSyncProtocolMessage(objectMessages: [InboundObjectMessage], protocolMessageChannelSerial _: String?) {
7887
receivedObjectSyncProtocolMessagesContinuation.yield(objectMessages)
7988
}
8089

8190
// MARK: - Sending `OBJECT` ProtocolMessage
8291

8392
// This is currently exposed so that we can try calling it from the tests in the early days of the SDK to check that we can send an OBJECT ProtocolMessage. We'll probably make it private later on.
8493
internal func testsOnly_sendObject(objectMessages: [OutboundObjectMessage]) async throws(InternalError) {
85-
guard let channel else {
86-
return
87-
}
88-
89-
try await DefaultInternalPlugin.sendObject(
90-
objectMessages: objectMessages,
91-
channel: channel,
92-
pluginAPI: pluginAPI,
93-
)
94+
try await coreSDK.sendObject(objectMessages: objectMessages)
9495
}
9596
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import Ably
2+
internal import AblyPlugin
3+
4+
/// The API that the internal components of the SDK (that is, `DefaultLiveObjects` and down) use to interact with our core SDK (i.e. ably-cocoa).
5+
///
6+
/// This provides us with a mockable interface to ably-cocoa, and it also allows internal components and their tests not to need to worry about some of the boring details of how we bridge Swift types to AblyPlugin's Objective-C API (i.e. boxing).
7+
internal protocol CoreSDK: AnyObject, Sendable {
8+
func sendObject(objectMessages: [OutboundObjectMessage]) async throws(InternalError)
9+
}
10+
11+
internal final class DefaultCoreSDK: CoreSDK {
12+
// We hold a weak reference to the channel so that `DefaultLiveObjects` can hold a strong reference to us without causing a strong reference cycle. We'll revisit this in https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/9.
13+
private let weakChannel: WeakRef<ARTRealtimeChannel>
14+
private let pluginAPI: PluginAPIProtocol
15+
16+
internal init(
17+
channel: ARTRealtimeChannel,
18+
pluginAPI: PluginAPIProtocol
19+
) {
20+
weakChannel = .init(referenced: channel)
21+
self.pluginAPI = pluginAPI
22+
}
23+
24+
// MARK: - Fetching channel
25+
26+
private var channel: ARTRealtimeChannel {
27+
guard let channel = weakChannel.referenced else {
28+
// It's currently completely possible that the channel _does_ become deallocated during the usage of the LiveObjects SDK; in https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/9 we'll figure out how to prevent this.
29+
preconditionFailure("Expected channel to not become deallocated during usage of LiveObjects SDK")
30+
}
31+
32+
return channel
33+
}
34+
35+
// MARK: - CoreSDK conformance
36+
37+
internal func sendObject(objectMessages: [OutboundObjectMessage]) async throws(InternalError) {
38+
try await DefaultInternalPlugin.sendObject(
39+
objectMessages: objectMessages,
40+
channel: channel,
41+
pluginAPI: pluginAPI,
42+
)
43+
}
44+
}

Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
3737
let logger = pluginAPI.logger(for: channel)
3838

3939
logger.log("LiveObjects.DefaultInternalPlugin received prepare(_:)", level: .debug)
40-
let liveObjects = DefaultRealtimeObjects(channel: channel, logger: logger, pluginAPI: pluginAPI)
40+
let coreSDK = DefaultCoreSDK(channel: channel, pluginAPI: pluginAPI)
41+
let liveObjects = DefaultRealtimeObjects(coreSDK: coreSDK, logger: logger)
4142
pluginAPI.setPluginDataValue(liveObjects, forKey: Self.pluginDataKey, channel: channel)
4243
}
4344

@@ -109,7 +110,7 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
109110
)
110111
}
111112

112-
internal func handleObjectSyncProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], protocolMessageChannelSerial: String, channel: ARTRealtimeChannel) {
113+
internal func handleObjectSyncProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], protocolMessageChannelSerial: String?, channel: ARTRealtimeChannel) {
113114
guard let inboundObjectMessageBoxes = publicObjectMessages as? [ObjectMessageBox<InboundObjectMessage>] else {
114115
preconditionFailure("Expected to receive the same InboundObjectMessage type as we emit")
115116
}

0 commit comments

Comments
 (0)