diff --git a/DXFeedFramework/Api/DXFeed.swift b/DXFeedFramework/Api/DXFeed.swift index 234dd1393..6c1780850 100644 --- a/DXFeedFramework/Api/DXFeed.swift +++ b/DXFeedFramework/Api/DXFeed.swift @@ -12,6 +12,10 @@ import Foundation public class DXFeed { /// Feed native wrapper. private let native: NativeFeed + + internal var nativeFeed: NativeFeed { + return native + } deinit { } @@ -287,3 +291,26 @@ public extension DXFeed { } } + +public extension DXFeed { + /// Attaches the given subscription to this feed. This method does nothing if the + /// corresponding subscription is already attached to this feed. + /// + /// This feed publishes data to the attached subscription. + /// - Parameters: + /// - subscription: The ``DXFeedSubscription``. + /// - Throws: ``GraalException``. Rethrows exception from Java. + func attach(subscription: DXFeedSubscription) throws { + try native.attach(subscription: subscription.nativeSubscription) + } + + /// Detaches the given subscription from this feed. This method does nothing if the + /// corresponding subscription is not attached to this feed. + /// + /// - Parameters: + /// - subscription: The ``DXFeedSubscription``. + /// - Throws: ``GraalException``. Rethrows exception from Java. + func detach(subscription: DXFeedSubscription) throws { + try native.detach(subscription: subscription.nativeSubscription) + } +} diff --git a/DXFeedFramework/Api/DXFeedSubscription.swift b/DXFeedFramework/Api/DXFeedSubscription.swift index af10a1da0..39b4ba47d 100644 --- a/DXFeedFramework/Api/DXFeedSubscription.swift +++ b/DXFeedFramework/Api/DXFeedSubscription.swift @@ -12,6 +12,10 @@ import Foundation public class DXFeedSubscription { /// Subscription native wrapper. private let native: NativeSubscription + + internal var nativeSubscription: NativeSubscription { + return native + } /// List of event types associated with this ``DXFeedSubscription`` fileprivate let types: [IEventType.Type] /// A set listeners of events @@ -156,3 +160,23 @@ extension DXFeedSubscription: IObservableSubscription { try native.removeChangeListener(listener) } } + +public extension DXFeedSubscription { + /// Attaches subscription to the specified feed. + /// + /// - Parameters: + /// - feed: The ``DXFeed`` to attach to. + /// - Throws: GraalException. Rethrows exception from Java. + func attach(feed: DXFeed) throws { + try native.attach(feed: feed.nativeFeed) + } + + /// Detaches subscription from the specified feed. + /// + /// - Parameters: + /// - feed: The ``DXFeed`` to detach from. + /// - Throws: GraalException. Rethrows exception from Java. + func detach(feed: DXFeed) throws { + try native.detach(feed: feed.nativeFeed) + } +} diff --git a/DXFeedFramework/Native/Feed/NativeFeed.swift b/DXFeedFramework/Native/Feed/NativeFeed.swift index 81d3881dd..2c5618b1f 100644 --- a/DXFeedFramework/Native/Feed/NativeFeed.swift +++ b/DXFeedFramework/Native/Feed/NativeFeed.swift @@ -226,4 +226,20 @@ class NativeFeed { toTime)) return NativePromise(promise: &native.pointee.base) } + + func attach(subscription: NativeSubscription) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, + dxfg_DXFeed_attachSubscription(thread, + feed, + subscription.subscription)) + } + + func detach(subscription: NativeSubscription) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, + dxfg_DXFeed_detachSubscription(thread, + feed, + subscription.subscription)) + } } diff --git a/DXFeedFramework/Native/Subscription/NativeSubscription.swift b/DXFeedFramework/Native/Subscription/NativeSubscription.swift index 558730164..f3f02d2ad 100644 --- a/DXFeedFramework/Native/Subscription/NativeSubscription.swift +++ b/DXFeedFramework/Native/Subscription/NativeSubscription.swift @@ -290,9 +290,27 @@ extension NativeSubscription { _ = try? ErrorCheck.nativeCall(thread, dxfg_CList_symbol_release(thread, nativeResult)) } - var result: [Symbol] = SymbolMapper.newSymbols(symbols: nativeResult).compactMap({ obj in + let result: [Symbol] = SymbolMapper.newSymbols(symbols: nativeResult).compactMap({ obj in obj as? Symbol }) return result } } + +extension NativeSubscription { + func attach(feed: NativeFeed) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, + dxfg_DXFeedSubscription_attach(thread, + subscription, + feed.feed)) + } + + func detach(feed: NativeFeed) throws { + let thread = currentThread() + try ErrorCheck.nativeCall(thread, + dxfg_DXFeedSubscription_detach(thread, + subscription, + feed.feed)) + } +} diff --git a/DXFeedFrameworkTests/FeedTest.swift b/DXFeedFrameworkTests/FeedTest.swift index e7f6bac0a..9fe8baa9e 100644 --- a/DXFeedFrameworkTests/FeedTest.swift +++ b/DXFeedFrameworkTests/FeedTest.swift @@ -62,4 +62,65 @@ final class FeedTest: XCTestCase { XCTAssert(false, "Subscription returned null") } } + + + func testAttachDetach() throws { + let detachedSymbol = "TEST1" + let attachedSymbol = "TEST2" + let endpoint = try DXEndpoint.create() + do { + if let feed = endpoint.getFeed(), let publisher = endpoint.getPublisher() { + let subcription = try feed.createSubscription([TimeAndSale.self]) + let expectation1 = expectation(description: "Events received") + let expectation2 = expectation(description: "Events received") + let listener = AnonymousClass { anonymCl in + anonymCl.callback = { events in + print(events) + events.forEach { event in + switch event.eventSymbol { + case detachedSymbol: + XCTFail("Received detached symbol \(event.toString())") + case attachedSymbol: + if event.timeAndSale.askPrice == 100 { + expectation1.fulfill() + } else if event.timeAndSale.askPrice == 200 { + expectation2.fulfill() + } + default: + XCTFail("Unexpected symbol \(event.toString())") + } + } + } + return anonymCl + } + try subcription.add(listener: listener) + try subcription.addSymbols(detachedSymbol) + try feed.detach(subscription: subcription) + try publisher.publish(events: [TimeAndSale(detachedSymbol)]) + try feed.attach(subscription: subcription) + try feed.attach(subscription: subcription) + try subcription.addSymbols(attachedSymbol) + + let tns1 = TimeAndSale(attachedSymbol) + tns1.askPrice = 100 + try publisher.publish(events: [tns1]) + wait(for: [expectation1], timeout: 1) + + try subcription.detach(feed: feed) + try publisher.publish(events: [TimeAndSale(detachedSymbol)]) + try subcription.attach(feed: feed) + tns1.askPrice = 200 + try publisher.publish(events: [tns1]) + wait(for: [expectation2], timeout: 1) + let symbols = try subcription.getSymbols().map { symbol in + symbol.stringValue + } + XCTAssert(Set(symbols) == Set([attachedSymbol, detachedSymbol])) + } else { + XCTAssert(false, "Subscription returned null") + } + } catch { + XCTAssert(false, "Error during attach/detach \(error)") + } + } }