iOS SDK
Through Yorkie iOS SDK, you can efficiently build collaborative applications. On the client-side implementation, you can create Documents that are automatically synced with remote peers with minimal effort.
If you want to install the SDK, refer to the Getting Started with iOS SDK.
Client
Client
is a normal client that can communicate with the server. It has Documents and sends changes of the Document from local to the server to synchronize with other replicas in remote.
Creating a Client
We can create a Client using Client(rpcAddress: RPCAddress(host:,port:), options:)
. After the Client has been activated, it is connected to the server and ready to use.
let client = Client(rpcAddress: RPCAddress(host: "api.yorkie.dev", port: 443), options: ClientOptions(apiKey: "xxxxxxxxxxxxxxxxxxxx"))try await client.activate()
Subscribing to Client events
We can use client.eventStream
to subscribe to client-based events, such as status-changed
, stream-connection-status-changed
and peer-changed
.
target.eventStream.sink { event inswitch event.type {case .statusChanged:()case .streamConnectionStatusChanged:()default:break}}
By using the value of the stream-connection-status-changed
event, it is possible to determine whether the Client is connected to the network.
If you want to know about other ClientEvents, please refer to the ClientEventType.
Presence
Presence is a feature that allows you to display information about users who are currently using a collaborative application. Presence is often used in collaborative applications such as document editors, chat apps, and other real-time applications.
var optionA = ClientOptions()optionA.presence = ["username": "alice", "color": "blue"]let clientA = Client(rpcAddress: rpcAddress, options: optionA)try await clientA.activate()let docA = Document(key: "doc-1")try await clientA.attach(docA)
Then, another Client is created and attaches a Document with the same name as before.
var optionB = ClientOptions()optionB.presence = ["username": "alice", "color": "blue"]let clientB = Client(rpcAddress: rpcAddress, options: optionA)try await clientB.activate()let docB = Document(key: "doc-1")try await clientB.attach(docB)
When a new peer registers or leaves, the peers-changed
event is fired, and the other peer's clientID and presence can be obtained from the event.
clientA.eventStream.sink { event inif let event = event as? PeerChangedEvent {let peers = event.value.peers["doc-1"]switch event.value.type {case .initialized:displayPeers(peers)case .watched:peers.forEach { addPeer($0) }// peer as follows:// {// clientID: 'xxxxxxxxxxxxxxxxxxxx',// presence: ["username": "bob", "color": "red"]// }case .unwatched:peers.forEach { removePeer($0) }case .presenceChanged:peers.forEach { updatePeer($0) }}}}
In the code above, clientA
receives a watched
event from clientB
because clientB
attached the Document with the key doc-1
.
Presence can include their names, colors, and other identifying details.
Document
Document
is a primary data type in Yorkie, which provides a JSON-like updating experience that makes it easy to represent your application's model. A Document
can be updated without being attached to the client, and its changes are automatically propagated to other peers when the Document
is attached to the Client
or when the network is restored.
Creating a Document
We can create a Document using Document(key: "doc-key")
. Let's create a Document with a key and attach it to the Client.
let doc = Document(key: docKey)try await client.attach(doc);
The document key is used to identify the Document in Yorkie. It is a string that can be freely defined by the user. However, it is allowed to use only a-z
, A-Z
, 0-9
, -
, .
, _
, ~
and must be less than 120 characters.
After attaching the Document to the Client, all changes to the Document are automatically synchronized with remote peers.
Changing Syncronization Mode
If you want to change syncronization mode, you can use client.pause(doc)
and client.resume(doc)
.
// Pause real-time sync.await client.pause(doc);// Resume real-time sync.await client.resume(doc);
Editing the Document
Document.update(:,message:)
enables you to modify a Document. The optional message
allows you to add a description to the change. If the Document is attached to the Client, all changes are automatically synchronized with other Clients.
let message = "update document for test";try await doc.update({ root inroot.obj = [:] // {"obj":{}}let obj = root.obj as! JSONObjectobj.num = Int64(1) // {"obj":{"num":1}}obj.obj = ["str": "a"] // {"obj":{"num":1,"obj":{"str":"a"}}}obj.arr = [Int64(1), Int64(2)] // {"obj":{"num":1,"obj":{"str":"a"},"arr":[1,2]}}}, message: message);
Under the hood, root
in the update
function creates a change
, a set of operations, using a JavaScript proxy. Every element has its unique ID, created by the logical clock. This ID is used by Yorkie to track which object is which.
You can get the contents of the Document using doc.getRoot()
.
let root = doc.getRoot()print(root.obj!) // {"num":1,"obj":{"str":"a"},"arr":[1,2]}let obj = root.obj as! JSONObjectprint(obj.num!) // 1print(obj.obj!) // {"str":"a"}print(obj.arr!) // [1,2]
Subscribing to Document events
A Document is modified by changes generated remotely or locally in Yorkie. When the Document is modified, change events occur, to which we can subscribe using document.subscribe
. Here, we can do post-processing such as repaint in the application using the path
of the change events.
await target.eventStream.sink { event inswitch event {case let event as LocalChangeEvent:print(event)case let event as RemoteChangeEvent:for changeInfo in event.value {print(changeInfo.change.message)for path in changeInfo.paths {if path.starts(with: "$.obj.num") {// root.obj.num is changed} else if path.starts(with: "$.obj") {// root.obj is changed}}}}}
Detaching the Document
If the document is no longer used, it should be detached to increase the efficiency of GC removing CRDT tombstones. For more information about GC, please refer to Garbage Collection.
try await client.detach(doc)
Custom CRDT types
Custom CRDT types are data types that can be used for special applications such as text editors and counters, unlike general JSON data types such as JSONObject
and JSONArray
. Custom CRDT types can be created in the callback function of document.update
.
JSONText
JSONText
provides supports for collaborative text editing. JSONText
has selection information for sharing the cursor position. In addition, contents in Text
can have attributes; for example, characters can be bold, italic, or underlined.
try await doc.update{ root inroot.text = JSONText() // {"text":[]}(root.text as? JSONText)?.edit(0, 0, "hello") // {"text":[{"val":"hello"}]}(root.text as? JSONText)?.edit(0, 1, "H") // {"text":[{"val":"H"},{"val":"ello"}]}(root.text as? JSONText)?.select(0, 1)(root.text as? JSONText)?.setStyle(fromIdx: 0, toIdx: 1, attributes: ["bold": true]) // {"text":[{"attrs":{"bold":"true"},"val":"H"},{"val":"ello"}]}}
An example of Text Editor: Text Editor example
JSONCounter
JSONCounter
supports integer types changing with addition and subtraction. If an integer data needs to be modified simultaneously, JSONCounter
should be used instead of primitives.
try await doc.update{ root inroot.counter = JSONCounter(value: Int64(1)) // {"counter":1}(root.counter as? JSONCounter<Int64>)?.increase(value: 2) // {"counter":3}(root.counter as? JSONCounter<Int64>)?.increase(value: 3) // {"counter":6}(root.counter as? JSONCounter<Int64>)?.increase(value: -4) // {"counter":2}}
Reference
For details on how to use the iOS SDK, please refer to iOS SDK Reference.