diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5668d408..192c53326 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: run: gradle fetchDependencies - name: Test - run: xcodebuild test -project DxFeedFramework.xcodeproj -scheme DXFeedFramework -testPlan DXExceptPublisherTests + run: xcodebuild test -project DxFeedFramework.xcodeproj -scheme DXFeedFramework -testPlan DXExceptPublisherTests -destination 'platform=macOS' release: if: (startsWith(github.event.ref, 'refs/tags/') && endsWith(github.event.ref, 'build')) diff --git a/DXFeedFramework.xcodeproj/xcshareddata/xcschemes/Tools.xcscheme b/DXFeedFramework.xcodeproj/xcshareddata/xcschemes/Tools.xcscheme index a65083b79..b9dfa1e6f 100644 --- a/DXFeedFramework.xcodeproj/xcshareddata/xcschemes/Tools.xcscheme +++ b/DXFeedFramework.xcodeproj/xcshareddata/xcschemes/Tools.xcscheme @@ -53,11 +53,11 @@ + isEnabled = "YES"> + isEnabled = "NO"> InstrumentProfileType? { + return InstrumentProfileType.find(self.type) + } +} diff --git a/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift b/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift index a5e21f91e..932b50f54 100644 --- a/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift +++ b/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift @@ -135,7 +135,7 @@ extension DXInstrumentProfileConnection: NativeIPFConnectionListener { func connectionDidChangeState(old: DXInstrumentProfileConnectionState, new: DXInstrumentProfileConnectionState) { observersSet.reader { items in let enumerator = items.objectEnumerator() - while let observer = enumerator.nextObject() as? DXInstrumentProfileConnection { + while let observer = enumerator.nextObject() as? DXInstrumentProfileConnectionObserver { observer.connectionDidChangeState(old: old, new: new) } } diff --git a/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift b/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift index 4f92a4ad2..608fa2a4e 100644 --- a/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift +++ b/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift @@ -10,18 +10,12 @@ import Foundation /// Native wrapper over the Java com.dxfeed.api.DXEndpoint class. class NativeEndpoint { - class WeakListener: WeakBox, EndpointListener { - func changeState(old: DXEndpointState, new: DXEndpointState) { - guard let endpoint = self.value else { - return - } - endpoint.changeState(old: old, new: new) - } - } - + private class WeakListener: WeakBox { } + private static let listeners = ConcurrentArray() + let endpoint: UnsafeMutablePointer! var listener: UnsafeMutablePointer? - static let listeners = ConcurrentArray() + private static let finalizeCallback: dxfg_finalize_function = { _, context in if let context = context { let endpoint: AnyObject = bridge(ptr: context) @@ -39,7 +33,7 @@ class NativeEndpoint { if let listener = endpoint as? WeakListener { var old = (try? EnumUtil.valueOf(value: DXEndpointState.convert(oldState))) ?? .notConnected var new = (try? EnumUtil.valueOf(value: DXEndpointState.convert(newState))) ?? .notConnected - listener.changeState(old: old, new: new) + listener.value?.changeState(old: old, new: new) } } } @@ -150,9 +144,4 @@ class NativeEndpoint { let value = try ErrorCheck.nativeCall(thread, dxfg_DXEndpoint_getState(thread, self.endpoint)) return try EnumUtil.valueOf(value: DXEndpointState.convert(value)) } - - func callGC() throws { - let thread = currentThread() - try ErrorCheck.nativeCall(thread, dxfg_gc(thread)) - } } diff --git a/DXFeedFramework/Native/Graal/Isolate.swift b/DXFeedFramework/Native/Graal/Isolate.swift index e64659bb7..c18f6890d 100644 --- a/DXFeedFramework/Native/Graal/Isolate.swift +++ b/DXFeedFramework/Native/Graal/Isolate.swift @@ -55,7 +55,7 @@ class Isolate { /// their tasks may be transferred to the overcommitted queue. /// Within the context of GraalVM, this transfer can result in the creation of a new thread, which might have already been attached to other tasks. /// This could lead to a fatalError, so it's crucial to carefully manage these processes and consider potential issues when working with the SDK." - init() { + private init() { #if DEBUG print("FEED SDK: Debug") #else @@ -84,4 +84,10 @@ class Isolate { fatalError(errorMessage) } } + + // only for testing + func callGC() { + let thread = currentThread() + _ = try? ErrorCheck.nativeCall(thread, dxfg_gc(thread)) + } } diff --git a/DXFeedFramework/Native/Ipf/InstrumentProfile+Ext.swift b/DXFeedFramework/Native/Ipf/InstrumentProfile+Ext.swift index 0b9977b23..f178bc8eb 100644 --- a/DXFeedFramework/Native/Ipf/InstrumentProfile+Ext.swift +++ b/DXFeedFramework/Native/Ipf/InstrumentProfile+Ext.swift @@ -62,6 +62,7 @@ extension InstrumentProfile { } func copy(to pointer: UnsafeMutablePointer) { + pointer.pointee.type = type.toCStringRef() pointer.pointee.symbol = symbol.toCStringRef() pointer.pointee.description = descriptionStr.toCStringRef() pointer.pointee.localSymbol = localSymbol.toCStringRef() @@ -92,5 +93,9 @@ extension InstrumentProfile { pointer.pointee.settlementStyle = settlementStyle.toCStringRef() pointer.pointee.priceIncrements = priceIncrements.toCStringRef() pointer.pointee.tradingHours = tradingHours.toCStringRef() + let list = UnsafeMutablePointer.allocate(capacity: 1) + list.pointee.size = 0 + list.pointee.elements = nil + pointer.pointee.customFields = list } } diff --git a/DXFeedFramework/Native/Ipf/InstrumentProfileMapper.swift b/DXFeedFramework/Native/Ipf/InstrumentProfileMapper.swift index dcbf834a4..43cd64b73 100644 --- a/DXFeedFramework/Native/Ipf/InstrumentProfileMapper.swift +++ b/DXFeedFramework/Native/Ipf/InstrumentProfileMapper.swift @@ -23,6 +23,8 @@ class InstrumentProfileMapper { } func releaseNative(native: UnsafeMutablePointer) { + native.pointee.customFields.deinitialize(count: 1) + native.pointee.customFields.deallocate() native.deinitialize(count: 1) native.deallocate() } diff --git a/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileCollector.swift b/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileCollector.swift index 07286224c..29c9f46a8 100644 --- a/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileCollector.swift +++ b/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileCollector.swift @@ -10,21 +10,35 @@ import Foundation /// Native wrapper over the Java com.dxfeed.ipf.live.InstrumentProfileCollector class. /// The location of the imported functions is in the header files "dxfg_ipf.h". -class NativeInstrumentProfileCollector { +public class NativeInstrumentProfileCollector { + private class WeakListener: WeakBox { } + private static let listeners = ConcurrentArray() + let collector: UnsafeMutablePointer? private var nativeListener: UnsafeMutablePointer? private weak var listener: DXInstrumentProfileUpdateListener? private static let mapper = InstrumentProfileMapper() - static let listenerCallback: dxfg_ipf_update_listener_function = {_, nativeProfiles, context in + private static let finalizeCallback: dxfg_finalize_function = { _, context in + if let context = context { + let endpoint: AnyObject = bridge(ptr: context) + if let listener = endpoint as? WeakListener { + NativeInstrumentProfileCollector.listeners.removeAll(where: { + return $0 === listener + }) + } + } + } + + private static let listenerCallback: dxfg_ipf_update_listener_function = {_, nativeProfiles, context in guard let nativeProfiles = nativeProfiles else { return } if let context = context { var profiles = [InstrumentProfile]() let listener: AnyObject = bridge(ptr: context) - if let listener = listener as? NativeInstrumentProfileCollector { + if let listener = listener as? WeakListener { let iterator = NativeProfileIterator(nativeProfiles) while (try? iterator.hasNext()) ?? false { @@ -32,11 +46,10 @@ class NativeInstrumentProfileCollector { let profile = try iterator.next() profiles.append(profile) } catch { - print("NativeInstrumentProfileCollector: excpetion \(error)") + print("NativeInstrumentProfileCollector: exception \(error)") } } - listener.listener?.instrumentProfilesUpdated(profiles) - + listener.value?.listener?.instrumentProfilesUpdated(profiles) } } } @@ -46,11 +59,12 @@ class NativeInstrumentProfileCollector { let thread = currentThread() _ = try? ErrorCheck.nativeCall(thread, dxfg_InstrumentProfileCollector_removeUpdateListener(thread, - self.collector, + collector, nativeListener)) _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(nativeListener.pointee.handler))) + self.nativeListener = nil self.listener = nil } } @@ -86,10 +100,10 @@ class NativeInstrumentProfileCollector { } let thread = currentThread() _ = try ErrorCheck.nativeCall(thread, - dxfg_InstrumentProfileCollector_updateInstrumentProfile( - thread, - collector, - native)) + dxfg_InstrumentProfileCollector_updateInstrumentProfile( + thread, + collector, + native)) } func view() throws -> NativeProfileIterator { @@ -116,13 +130,23 @@ class NativeInstrumentProfileCollector { removeListener() let thread = currentThread() self.listener = listener - let voidPtr = bridge(obj: self) + + let weakListener = WeakListener(value: self) + NativeInstrumentProfileCollector.listeners.append(newElement: weakListener) + let voidPtr = bridge(obj: weakListener) + let callback = NativeInstrumentProfileCollector.listenerCallback - let listener = try ErrorCheck.nativeCall(thread, + let nativeListener = try ErrorCheck.nativeCall(thread, dxfg_InstrumentProfileUpdateListener_new(thread, callback, voidPtr)) - self.nativeListener = listener + self.nativeListener = nativeListener + + try ErrorCheck.nativeCall(thread, dxfg_Object_finalize(thread, + &(nativeListener.pointee.handler), + NativeInstrumentProfileCollector.finalizeCallback, + voidPtr)) + _ = try ErrorCheck.nativeCall(thread, dxfg_InstrumentProfileCollector_addUpdateListener(thread, self.collector, diff --git a/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileConnection.swift b/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileConnection.swift index 6966fe5f7..a8b79c2df 100644 --- a/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileConnection.swift +++ b/DXFeedFramework/Native/Ipf/Live/NativeInstrumentProfileConnection.swift @@ -11,6 +11,9 @@ import Foundation /// Native wrapper over the Java com.dxfeed.ipf.live.InstrumentProfileConnection class. /// The location of the imported functions is in the header files "dxfg_ipf.h". class NativeInstrumentProfileConnection { + private class WeakListener: WeakBox { } + private static let listeners = ConcurrentArray() + private let connection: UnsafeMutablePointer private let address: String @@ -19,14 +22,25 @@ class NativeInstrumentProfileConnection { var nativeListener: UnsafeMutablePointer? private weak var listener: NativeIPFConnectionListener? - static let listenerCallback: dxfg_ipf_connection_state_change_listener_func = {_, oldState, newState, context in + private static let finalizeCallback: dxfg_finalize_function = { _, context in if let context = context { let endpoint: AnyObject = bridge(ptr: context) - if let listener = endpoint as? NativeInstrumentProfileConnection { + if let listener = endpoint as? WeakListener { + NativeInstrumentProfileConnection.listeners.removeAll(where: { + return $0 === listener + }) + } + } + } + + private static let listenerCallback: dxfg_ipf_connection_state_change_listener_func = {_, oldState, newState, context in + if let context = context { + let endpoint: AnyObject = bridge(ptr: context) + if let listener = endpoint as? WeakListener { var old = (try? EnumUtil.valueOf(value: DXInstrumentProfileConnectionState.convert(oldState))) var new = (try? EnumUtil.valueOf(value: DXInstrumentProfileConnectionState.convert(newState))) - listener.listener?.connectionDidChangeState(old: old ?? .notConnected, - new: new ?? .notConnected) + listener.value?.listener?.connectionDidChangeState(old: old ?? .notConnected, + new: new ?? .notConnected) } } } @@ -39,6 +53,7 @@ class NativeInstrumentProfileConnection { connection, listener)) _ = try? ErrorCheck.nativeCall(thread, dxfg_JavaObjectHandler_release(thread, &(listener.pointee.handler))) + self.nativeListener = nil self.listener = nil } } @@ -128,13 +143,21 @@ class NativeInstrumentProfileConnection { func addListener(_ listener: NativeIPFConnectionListener) throws { removeListener() self.listener = listener - let voidPtr = bridge(obj: self) + let weakListener = WeakListener(value: self) + NativeInstrumentProfileConnection.listeners.append(newElement: weakListener) + let voidPtr = bridge(obj: weakListener) let thread = currentThread() let listener = try ErrorCheck.nativeCall(thread, dxfg_IpfPropertyChangeListener_new( thread, NativeInstrumentProfileConnection.listenerCallback, voidPtr)) + + try ErrorCheck.nativeCall(thread, dxfg_Object_finalize(thread, + &(listener.pointee.handler), + NativeInstrumentProfileConnection.finalizeCallback, + voidPtr)) + self.nativeListener = listener try ErrorCheck.nativeCall(currentThread(), diff --git a/DXFeedFramework/Native/Subscription/NativeSubscription.swift b/DXFeedFramework/Native/Subscription/NativeSubscription.swift index eb172d207..f480dca72 100644 --- a/DXFeedFramework/Native/Subscription/NativeSubscription.swift +++ b/DXFeedFramework/Native/Subscription/NativeSubscription.swift @@ -11,24 +11,42 @@ import Foundation /// Native wrapper over the Java com.dxfeed.api.DxFeedSubscription class. /// The location of the imported functions is in the header files "dxfg_subscription.h". class NativeSubscription { + private class WeakSubscription: WeakBox { } + private static let listeners = ConcurrentArray() + let subscription: UnsafeMutablePointer? var nativeListener: UnsafeMutablePointer? private let mapper = EventMapper() weak var listener: DXEventListener? + + private static let finalizeCallback: dxfg_finalize_function = { _, context in + if let context = context { + let endpoint: AnyObject = bridge(ptr: context) + if let listener = endpoint as? WeakSubscription { + NativeSubscription.listeners.removeAll(where: { + return $0 === listener + }) + } + } + } + static let listenerCallback: dxfg_feed_event_listener_function = {_, nativeEvents, context in if let context = context { var events = [MarketEvent]() let listener: AnyObject = bridge(ptr: context) - if let listener = listener as? NativeSubscription { + if let listener = listener as? WeakSubscription { + guard let subscription = listener.value else { + return + } let count = Int(nativeEvents?.pointee.size ?? 0) for index in 0.. 0 { expectationCollector.fulfill() } } return anonymCl - }) + } + try collector.add(observer: collectorObserver) let expectationConnection = expectation(description: "Connection") expectationConnection.expectedFulfillmentCount = 3 // connecting, connected, completed let connection = try DXInstrumentProfileConnection(address, collector) - connection.add(observer: AnonymousConnectionListener { anonymCl in + let connectionObserver = AnonymousConnectionListener { anonymCl in anonymCl.callback = { _, new in switch new { case .notConnected: @@ -239,10 +242,10 @@ STOCK,EREGL:TR,EREĞLİ DEMİR VE ÇELİK FABRİKALARI1 T.A.Ş.,TR,XIST,XIST,TRY } } return anonymCl - }) + } + connection.add(observer: connectionObserver) try connection.start() wait(for: [expectationConnection, expectationCollector], timeout: 20.0) - } func testCreateOnScheduledThreadPool() throws { diff --git a/DXFeedFrameworkTests/IsolateTest.swift b/DXFeedFrameworkTests/IsolateTest.swift index 427088102..19dc20d31 100644 --- a/DXFeedFrameworkTests/IsolateTest.swift +++ b/DXFeedFrameworkTests/IsolateTest.swift @@ -19,6 +19,7 @@ final class IsolateTest: XCTestCase { } func testCleanup() throws { + throw XCTSkip("Just for manual running") let isolate = Isolate.shared isolate.cleanup() let sec = 5