Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated yrs to 0.18.2 #44

Merged
merged 8 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions Sources/YSwift/YArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,25 +134,19 @@ public final class YArray<T: Codable>: Transactable, YCollection {
/// Returns a publisher of array changes.
public func observe() -> AnyPublisher<[YArrayChange<T>], Never> {
let subject = PassthroughSubject<[YArrayChange<T>], Never>()
let subscriptionId = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: { [weak self] in
self?._array.unobserve(subscriptionId: subscriptionId)
let subscription = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: {
subscription.cancel()
})
.eraseToAnyPublisher()
}

/// Registers a closure that is called with an array of changes to the list.
/// - Parameter body: A closure that is called with an array of list changes.
/// - Returns: An observer identifier.
public func observe(_ body: @escaping ([YArrayChange<T>]) -> Void) -> UInt32 {
public func observe(_ body: @escaping ([YArrayChange<T>]) -> Void) -> YSubscription {
let delegate = YArrayObservationDelegate(callback: body, decoded: Coder.decodedArray)
return _array.observe(delegate: delegate)
}

/// Unregister an observing closure, identified by the identifier you provide.
/// - Parameter subscriptionId: The observer identifier to unregister.
public func unobserve(_ subscriptionId: UInt32) {
_array.unobserve(subscriptionId: subscriptionId)
return YSubscription(subscription: _array.observe(delegate: delegate))
}

public func pointer() -> YrsCollectionPtr {
Expand Down
16 changes: 5 additions & 11 deletions Sources/YSwift/YMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,25 +155,19 @@ public final class YMap<T: Codable>: Transactable, YCollection {
/// Returns a publisher of map changes.
public func observe() -> AnyPublisher<[YMapChange<T>], Never> {
let subject = PassthroughSubject<[YMapChange<T>], Never>()
let subscriptionId = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: { [weak self] in
self?._map.unobserve(subscriptionId: subscriptionId)
let subscription = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: {
subscription.cancel()
})
.eraseToAnyPublisher()
}

/// Registers a closure that is called with an array of changes to the map.
/// - Parameter body: A closure that is called with an array of map changes.
/// - Returns: An observer identifier.
public func observe(_ body: @escaping ([YMapChange<T>]) -> Void) -> UInt32 {
public func observe(_ body: @escaping ([YMapChange<T>]) -> Void) -> YSubscription {
let delegate = YMapObservationDelegate(decoded: Coder.decoded, callback: body)
return _map.observe(delegate: delegate)
}

/// Unregister an observing closure, identified by the identifier you provide.
/// - Parameter subscriptionId: The observer identifier to unregister.
public func unobserve(_ subscriptionId: UInt32) {
_map.unobserve(subscriptionId: subscriptionId)
return YSubscription(subscription: _map.observe(delegate: delegate))
}

public func toMap(transaction: YrsTransaction? = nil) -> [String: T] {
Expand Down
21 changes: 21 additions & 0 deletions Sources/YSwift/YSubscription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation
import Yniffi

/// Handler for an active subscription.
/// Once the subscription is deinitialized, it will automatically unsubscribe.
/// You can explicitly cancel the subscription by calling `cancel`.
public final class YSubscription {
private var subscription: Yniffi.YSubscription?

init(subscription: Yniffi.YSubscription) {
self.subscription = subscription
}

public func cancel() {
heckj marked this conversation as resolved.
Show resolved Hide resolved
subscription = nil
}

deinit {
cancel()
}
}
24 changes: 10 additions & 14 deletions Sources/YSwift/YText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,30 +175,26 @@ public final class YText: Transactable, YCollection {
/// Returns a publisher of changes for the text.
public func observe() -> AnyPublisher<[YTextChange], Never> {
let subject = PassthroughSubject<[YTextChange], Never>()
let subscriptionId = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: { [weak self] in
self?._text.unobserve(subscriptionId: subscriptionId)
let subscription = observe { subject.send($0) }
return subject.handleEvents(receiveCancel: {
subscription.cancel()
})
.eraseToAnyPublisher()
}

/// Registers a closure that is called with an array of changes to the text.
/// - Parameter callback: The closure to process reported changes from the text.
/// - Returns: An observer identifier that you can use to stop observing the text.
public func observe(_ callback: @escaping ([YTextChange]) -> Void) -> UInt32 {
_text.observe(
delegate: YTextObservationDelegate(
callback: callback,
decoded: Coder.decoded(_:)
public func observe(_ callback: @escaping ([YTextChange]) -> Void) -> YSubscription {
YSubscription(
subscription: _text.observe(
delegate: YTextObservationDelegate(
callback: callback,
decoded: Coder.decoded(_:)
)
)
)
}

/// Unregister an observing closure, identified by the identifier you provide.
/// - Parameter subscriptionId: The observer identifier to unregister.
public func unobserve(_ subscriptionId: UInt32) {
_text.unobserve(subscriptionId: subscriptionId)
}

public func pointer() -> YrsCollectionPtr {
return _text.rawPtr()
Expand Down
30 changes: 6 additions & 24 deletions Sources/YSwift/YUndoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,47 +64,29 @@ public final class YUndoManager<T: AnyObject> {
/// - Returns: A handle to the subscription.
///
/// Cancel the subscription by calling ``unobserveAdded(_:)`` with the subscription Id this method returns.
public func observeAdded(_ body: @escaping (UndoEvent, T?) -> T?) -> UInt32 {
public func observeAdded(_ body: @escaping (UndoEvent, T?) -> T?) -> YSubscription {
let delegate = YUndoManagerObservationDelegate(callback: body)
return _manager.observeAdded(delegate: delegate)
}

/// Cancels a subscription to the undo manager adding changes.
/// - Parameter subscriptionId: The subscription Id to cancel.
public func unobserveAdded(_ subscriptionId: UInt32) {
return _manager.unobserveAdded(subscriptionId: subscriptionId)
return YSubscription(subscription: _manager.observeAdded(delegate: delegate))
}

/// Creates a subscription that is called when the undo manager updates a change.
/// - Parameter body: A closure that provides an ``UndoEvent`` about the changes that were updated..
/// - Returns: A handle to the subscription.
///
/// Call ``unobserveUpdated(_:)`` with the subscription Id this method returns to cancel the subscription.
public func observeUpdated(_ body: @escaping (UndoEvent, T?) -> T?) -> UInt32 {
public func observeUpdated(_ body: @escaping (UndoEvent, T?) -> T?) -> YSubscription {
let delegate = YUndoManagerObservationDelegate(callback: body)
return _manager.observeUpdated(delegate: delegate)
}

/// Cancels a subscription to the undo manager updating changes.
/// - Parameter subscriptionId: The subscription to be cancalled.
public func unobserveUpdated(_ subscriptionId: UInt32) {
return _manager.unobserveUpdated(subscriptionId: subscriptionId)
return YSubscription(subscription: _manager.observeUpdated(delegate: delegate))
}

/// Creates a subscription that is called when the undo manager replays a change.
/// - Parameter body: A closure that provides an ``UndoEvent`` about the changes being replayed.
/// - Returns: A handle to the subscription.
///
/// Call ``unobserveUpdated(_:)`` with the subscription Id this method returns to cancel the subscription.
public func observePopped(_ body: @escaping (UndoEvent, T?) -> T?) -> UInt32 {
public func observePopped(_ body: @escaping (UndoEvent, T?) -> T?) -> YSubscription {
let delegate = YUndoManagerObservationDelegate(callback: body)
return _manager.observePopped(delegate: delegate)
}

/// Cancels a subscription that is called when the undo manager replays a change.
/// - Parameter subscriptionId: The subscription to be cancelled.
public func unobservePopped(_ subscriptionId: UInt32) {
return _manager.unobservePopped(subscriptionId: subscriptionId)
return YSubscription(subscription: _manager.observePopped(delegate: delegate))
}
}

Expand Down
14 changes: 8 additions & 6 deletions Tests/YSwiftTests/YArrayTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class YArrayTests: XCTestCase {
let insertedElements = [TestType(name: "Aidar", age: 24), TestType(name: "Joe", age: 55)]
var receivedElements: [TestType] = []

let subscriptionId = array.observe { changes in
let subscription = array.observe { changes in
changes.forEach {
switch $0 {
case let .added(elements):
Expand All @@ -175,7 +175,7 @@ class YArrayTests: XCTestCase {

array.insertArray(at: 0, values: insertedElements)

array.unobserve(subscriptionId)
subscription.cancel()

XCTAssertEqual(insertedElements, receivedElements)
}
Expand All @@ -191,7 +191,7 @@ class YArrayTests: XCTestCase {
var object = NSObject()
weak var weakObject = object

let _ = array.observe { [object] changes in
let subscription = array.observe { [object] changes in
// Capture the object in the closure (note that we need to use
// a capture list like [object] above in order for the object
// to be captured by reference instead of by pointer value)
Expand All @@ -204,6 +204,8 @@ class YArrayTests: XCTestCase {
// Because we didn't explicitly unobserved/unsubscribed.
object = NSObject()
XCTAssertNotNil(weakObject)

subscription.cancel()
}

func test_observation_closure_IsNotLeakingAfterUnobserving() {
Expand All @@ -212,16 +214,16 @@ class YArrayTests: XCTestCase {
var object = NSObject()
weak var weakObject = object

let subscriptionId = array.observe { [object] changes in
let subscription = array.observe { [object] changes in
// Capture the object in the closure (note that we need to use
// a capture list like [object] above in order for the object
// to be captured by reference instead of by pointer value)
_ = object
changes.forEach { _ in }
}

// Explicit unobserving, to prevent leaking
array.unobserve(subscriptionId)
subscription.cancel()

// When we re-assign our local strong reference to a new object the
// weak reference should become nil, since the closure should
Expand Down
12 changes: 7 additions & 5 deletions Tests/YSwiftTests/YMapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ final class YMapTests: XCTestCase {

var actualChanges: [YMapChange<TestType>] = []

let subscriptionId = map.observe { changes in
let subscription = map.observe { changes in
changes.forEach { change in
actualChanges.append(change)
}
Expand All @@ -132,7 +132,7 @@ final class YMapTests: XCTestCase {
map[first.name] = nil
map[second.name] = updatedSecond

map.unobserve(subscriptionId)
subscription.cancel()

// Use set here, to compare two arrays by the composition not by order
XCTAssertEqual(
Expand All @@ -157,7 +157,7 @@ final class YMapTests: XCTestCase {
var object = NSObject()
weak var weakObject = object

let _ = map.observe { [object] changes in
let subscription = map.observe { [object] changes in
// Capture the object in the closure (note that we need to use
// a capture list like [object] above in order for the object
// to be captured by reference instead of by pointer value)
Expand All @@ -170,6 +170,8 @@ final class YMapTests: XCTestCase {
// Because we didn't explicitly unobserved/unsubscribed.
object = NSObject()
XCTAssertNotNil(weakObject)

subscription.cancel()
}

func test_observation_closure_IsNotLeakingAfterUnobserving() {
Expand All @@ -178,7 +180,7 @@ final class YMapTests: XCTestCase {
var object = NSObject()
weak var weakObject = object

let subscriptionId = map.observe { [object] changes in
let subscription = map.observe { [object] changes in
// Capture the object in the closure (note that we need to use
// a capture list like [object] above in order for the object
// to be captured by reference instead of by pointer value)
Expand All @@ -187,7 +189,7 @@ final class YMapTests: XCTestCase {
}

// Explicit unobserving, to prevent leaking
map.unobserve(subscriptionId)
subscription.cancel()

// When we re-assign our local strong reference to a new object the
// weak reference should become nil, since the closure should
Expand Down
56 changes: 56 additions & 0 deletions Tests/YSwiftTests/YSubscriptionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Foundation

import Combine
import XCTest
@testable import YSwift

class YSubscriptionTests: XCTestCase {
var document: YDocument!
var text: YText!

override func setUp() {
document = YDocument()
text = document.getOrCreateText(named: "test")
}

override func tearDown() {
document = nil
text = nil
}

private func test_cancel(cancel: (inout YSubscription?) -> Void) {
// Create an object (it can be of any type), and hold both
// a strong and a weak reference to it
var object = NSObject()
weak var weakObject = object

var subscription: YSubscription? = text.observe { _ in
// Capture the object in the closure (note that we need to use
// a capture list like [object] above in order for the object
// to be captured by reference instead of by pointer value)
_ = object
}

// Explicit unobserving, to prevent leaking
cancel(&subscription)

// When we re-assign our local strong reference to a new object the
// weak reference should become nil, since the closure should
// have been run and removed at this point
// Because we did explicitly unobserve/unsubscribe at this point.
object = NSObject()
XCTAssertNil(weakObject)
}

func test_cancel_deinit() {
test_cancel { subscription in
subscription = nil
}
}

func test_cancel_explicit() {
test_cancel { subscription in
subscription?.cancel()
}
}
}
Loading
Loading