diff --git a/DXFeedFramework.xcodeproj/project.pbxproj b/DXFeedFramework.xcodeproj/project.pbxproj index 10b28822b..98261d148 100644 --- a/DXFeedFramework.xcodeproj/project.pbxproj +++ b/DXFeedFramework.xcodeproj/project.pbxproj @@ -57,11 +57,11 @@ 641BCBC12A21077800FE23C2 /* EventCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BCBC02A21077800FE23C2 /* EventCode.swift */; }; 641BDD582AC71CCE00236B78 /* LatencyTestCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD572AC71CCE00236B78 /* LatencyTestCommand.swift */; }; 641BDD592AC7215200236B78 /* LatencyDiagnostic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6417DD1E2A39C9A7008912D6 /* LatencyDiagnostic.swift */; }; + 641BDD5B2AC72BD400236B78 /* ConcurrentWeakHashTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD5A2AC72BD400236B78 /* ConcurrentWeakHashTable.swift */; }; 641BDD5D2ACD67A000236B78 /* LatencyListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD5C2ACD67A000236B78 /* LatencyListener.swift */; }; 641BDD5E2ACD67A400236B78 /* LatencyListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD5C2ACD67A000236B78 /* LatencyListener.swift */; }; 641BDD612ACD697B00236B78 /* AbstractEventListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD602ACD697B00236B78 /* AbstractEventListener.swift */; }; 641BDD622ACD697B00236B78 /* AbstractEventListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD602ACD697B00236B78 /* AbstractEventListener.swift */; }; - 641BDD5B2AC72BD400236B78 /* ConcurentWeakSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 641BDD5A2AC72BD400236B78 /* ConcurentWeakSet.swift */; }; 642528D02A3C534D00A04E41 /* TimeInterval+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6469F8CD2A3B2F9900846831 /* TimeInterval+Ext.swift */; }; 642528D12A3C534D00A04E41 /* TimeInterval+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6469F8CD2A3B2F9900846831 /* TimeInterval+Ext.swift */; }; 64262CCE2A4DA64700BA6BA3 /* RealityKitContent in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = 64262CCD2A4DA64700BA6BA3 /* RealityKitContent */; }; @@ -516,9 +516,9 @@ 641BCBBB2A20ED8100FE23C2 /* DXEndpointObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DXEndpointObserver.swift; sourceTree = ""; }; 641BCBC02A21077800FE23C2 /* EventCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCode.swift; sourceTree = ""; }; 641BDD572AC71CCE00236B78 /* LatencyTestCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatencyTestCommand.swift; sourceTree = ""; }; + 641BDD5A2AC72BD400236B78 /* ConcurrentWeakHashTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentWeakHashTable.swift; sourceTree = ""; }; 641BDD5C2ACD67A000236B78 /* LatencyListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatencyListener.swift; sourceTree = ""; }; 641BDD602ACD697B00236B78 /* AbstractEventListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractEventListener.swift; sourceTree = ""; }; - 641BDD5A2AC72BD400236B78 /* ConcurentWeakSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurentWeakSet.swift; sourceTree = ""; }; 64262CC92A4DA64600BA6BA3 /* DXVisionQuoteTableApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DXVisionQuoteTableApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 64262CCC2A4DA64700BA6BA3 /* RealityKitContent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealityKitContent; sourceTree = ""; }; 64262CCF2A4DA64700BA6BA3 /* VisionQuoteTableAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisionQuoteTableAppApp.swift; sourceTree = ""; }; @@ -1009,7 +1009,7 @@ 64656F642A1CAFC5006A0B19 /* String+Pointee.swift */, 64656F6A2A1CFAC2006A0B19 /* BridgeUtil.swift */, 64656F6E2A1CFC12006A0B19 /* WeakBox.swift */, - 641BDD5A2AC72BD400236B78 /* ConcurentWeakSet.swift */, + 641BDD5A2AC72BD400236B78 /* ConcurrentWeakHashTable.swift */, 64104FC42A26059B00D1FC41 /* ConcurrentSet.swift */, 64104FC62A2613BC00D1FC41 /* ConcurrentArray.swift */, 6469F8C12A3B169A00846831 /* MathUtil.swift */, @@ -2087,7 +2087,7 @@ 8088D77329C3A2F400F240CB /* GraalException.swift in Sources */, 64C771FF2A9504ED009868C2 /* SnapshotProcessor.swift in Sources */, 64C771F82A94B88C009868C2 /* TimeAndSaleType.swift in Sources */, - 641BDD5B2AC72BD400236B78 /* ConcurentWeakSet.swift in Sources */, + 641BDD5B2AC72BD400236B78 /* ConcurrentWeakHashTable.swift in Sources */, 6447A5DF2A8E56FC00739CCF /* IIndexedEvent.swift in Sources */, 64104FC52A26059B00D1FC41 /* ConcurrentSet.swift in Sources */, 64BA925F2A306B9600BE26A0 /* Profile.swift in Sources */, diff --git a/DXFeedFramework/Api/DXEndpoint.swift b/DXFeedFramework/Api/DXEndpoint.swift index af7408012..f26a29979 100644 --- a/DXFeedFramework/Api/DXEndpoint.swift +++ b/DXFeedFramework/Api/DXEndpoint.swift @@ -187,13 +187,7 @@ public class DXEndpoint { DXPublisher() }() /// A list of state change listeners callback. observersSet - not typed variable(as storage). - /// observers - typed list wrapper. - private var observersSet = ConcurrentWeakSet() - private var observers: [DXEndpointObserver] { - return observersSet.reader { - $0.allObjects.compactMap { value in value as? DXEndpointObserver } - } - } + private var observersSet = ConcurrentWeakHashTable() private static var instances = [Role: DXEndpoint]() @@ -512,6 +506,11 @@ public class Builder { extension DXEndpoint: EndpointListener { func changeState(old: DXEndpointState, new: DXEndpointState) { - observers.forEach { $0.endpointDidChangeState(old: old, new: new) } + observersSet.reader { + let enumerator = $0.objectEnumerator() + while let observer = enumerator.nextObject() as? DXEndpointObserver { + observer.endpointDidChangeState(old: old, new: new) + } + } } } diff --git a/DXFeedFramework/Api/DXFeedSubcription.swift b/DXFeedFramework/Api/DXFeedSubcription.swift index 79c54a403..9ad2e08e9 100644 --- a/DXFeedFramework/Api/DXFeedSubcription.swift +++ b/DXFeedFramework/Api/DXFeedSubcription.swift @@ -17,7 +17,7 @@ public class DXFeedSubcription { fileprivate let events: Set /// A set listeners of events /// observers - typed list wrapper. - private let listeners = ConcurrentWeakSet() + private let listeners = ConcurrentWeakHashTable() /// - Throws: ``GraalException`` Rethrows exception from Java, ``ArgumentException/argumentNil`` internal init(native: NativeSubscription?, events: [EventCode]) throws { @@ -96,7 +96,10 @@ public class DXFeedSubcription { extension DXFeedSubcription: DXEventListener { public func receiveEvents(_ events: [MarketEvent]) { listeners.reader { items in - items.allObjects.compactMap { $0 as? DXEventListener }.forEach { $0.receiveEvents(events) } + let enumerator = items.objectEnumerator() + while let observer = enumerator.nextObject() as? DXEventListener { + observer.receiveEvents(events) + } } } } diff --git a/DXFeedFramework/Ipf/Live/DXInstrumentProfileCollector.swift b/DXFeedFramework/Ipf/Live/DXInstrumentProfileCollector.swift index 04e42507b..448ef5897 100644 --- a/DXFeedFramework/Ipf/Live/DXInstrumentProfileCollector.swift +++ b/DXFeedFramework/Ipf/Live/DXInstrumentProfileCollector.swift @@ -20,7 +20,7 @@ import Foundation /// Removal of instrument profile is represented by an ``InstrumentProfile`` instance with a /// ``InstrumentProfile/type`` equal to ``InstrumentProfileType/removed`` public class DXInstrumentProfileCollector { - private let listeners = ConcurrentWeakSet() + private let listeners = ConcurrentWeakHashTable() let native: NativeInstrumentProfileCollector /// Creates instrument profile connection. @@ -128,9 +128,10 @@ public class DXInstrumentProfileCollector { extension DXInstrumentProfileCollector: DXInstrumentProfileUpdateListener { public func instrumentProfilesUpdated(_ instruments: [InstrumentProfile]) { listeners.reader { items in - items.allObjects.compactMap { - $0 as? DXInstrumentProfileUpdateListener - }.forEach { $0.instrumentProfilesUpdated(instruments) } + let enumerator = items.objectEnumerator() + while let observer = enumerator.nextObject() as? DXInstrumentProfileUpdateListener { + observer.instrumentProfilesUpdated(instruments) + } } } } diff --git a/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift b/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift index 90e9efb55..a5e21f91e 100644 --- a/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift +++ b/DXFeedFramework/Ipf/Live/DXInstrumentProfileConnection.swift @@ -25,10 +25,7 @@ public class DXInstrumentProfileConnection { private let native: NativeInstrumentProfileConnection private let collector: DXInstrumentProfileCollector - private var observersSet = ConcurrentWeakSet() - private var observers: [DXInstrumentProfileConnectionObserver] { - return observersSet.reader { $0.allObjects.compactMap { value in value as? DXInstrumentProfileConnectionObserver } } - } + private var observersSet = ConcurrentWeakHashTable() /// Creates instrument profile connection with a specified address and collector. /// @@ -136,6 +133,11 @@ public class DXInstrumentProfileConnection { extension DXInstrumentProfileConnection: NativeIPFConnectionListener { func connectionDidChangeState(old: DXInstrumentProfileConnectionState, new: DXInstrumentProfileConnectionState) { - observers.forEach { $0.connectionDidChangeState(old: old, new: new) } + observersSet.reader { items in + let enumerator = items.objectEnumerator() + while let observer = enumerator.nextObject() as? DXInstrumentProfileConnection { + observer.connectionDidChangeState(old: old, new: new) + } + } } } diff --git a/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift b/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift index 199e68f7b..4f92a4ad2 100644 --- a/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift +++ b/DXFeedFramework/Native/Endpoint/NativeEndpoint.swift @@ -81,7 +81,7 @@ class NativeEndpoint { func getNativeFeed() -> NativeFeed? { return self.feed } - func addListener(_ listener: any EndpointListener) throws { + func addListener(_ listener: EndpointListener) throws { removeListener() let weakListener = WeakListener(value: listener) NativeEndpoint.listeners.append(newElement: weakListener) diff --git a/DXFeedFramework/Utils/ConcurrentSet.swift b/DXFeedFramework/Utils/ConcurrentSet.swift index f90546fd9..cdbbae7cc 100644 --- a/DXFeedFramework/Utils/ConcurrentSet.swift +++ b/DXFeedFramework/Utils/ConcurrentSet.swift @@ -24,7 +24,6 @@ public class ConcurrentSet: CustomStringConvertible where T: Hashable { } public func insert(_ newMember: T) { - let weakValue = WeakBox(value: newMember) writer { $0.insert(newMember) } } diff --git a/DXFeedFramework/Utils/ConcurentWeakSet.swift b/DXFeedFramework/Utils/ConcurrentWeakHashTable.swift similarity index 56% rename from DXFeedFramework/Utils/ConcurentWeakSet.swift rename to DXFeedFramework/Utils/ConcurrentWeakHashTable.swift index cb9320f43..b2e2b7d53 100644 --- a/DXFeedFramework/Utils/ConcurentWeakSet.swift +++ b/DXFeedFramework/Utils/ConcurrentWeakHashTable.swift @@ -1,5 +1,5 @@ // -// ConcurentWeakSet.swift +// ConcurrentWeakHashTable.swift // DXFeedFramework // // Created by Aleksey Kosylo on 29.09.23. @@ -7,8 +7,8 @@ import Foundation -public class ConcurrentWeakSet { - private var set: NSHashTable = .weakObjects() +public class ConcurrentWeakHashTable { + internal var set: NSHashTable = .weakObjects() private let accessQueue = DispatchQueue(label: "com.dxfeed.set_nshashtable", attributes: .concurrent) public var count: Int { @@ -16,22 +16,24 @@ public class ConcurrentWeakSet { } public func insert(_ newMember: T) { - writer { $0.add(newMember) } + writer { + $0.add(newMember as AnyObject) + } } public func remove(_ member: T) { - writer { $0.remove(member) } + writer { $0.remove(member as AnyObject) } } public func removeAll() { writer { $0.removeAllObjects() } } - public func reader(_ block: (NSHashTable) throws -> U) rethrows -> U { + public func reader(_ block: (NSHashTable) throws -> U) rethrows -> U { try accessQueue.sync { try block(set) } } - public func writer(_ block: @escaping (inout NSHashTable) -> Void) { + public func writer(_ block: @escaping (inout NSHashTable) -> Void) { accessQueue.async(flags: .barrier) { block(&self.set) } } } diff --git a/DXFeedFrameworkTests/DXFeedFrameworkTests.xctestplan b/DXFeedFrameworkTests/DXFeedFrameworkTests.xctestplan index 6b7a62006..fd5063c64 100644 --- a/DXFeedFrameworkTests/DXFeedFrameworkTests.xctestplan +++ b/DXFeedFrameworkTests/DXFeedFrameworkTests.xctestplan @@ -15,7 +15,8 @@ ], "environmentVariableEntries" : [ - ] + ], + "testRepetitionMode" : "retryOnFailure" }, "testTargets" : [ { diff --git a/DXFeedFrameworkTests/EndpointPublisherTest.swift b/DXFeedFrameworkTests/EndpointPublisherTest.swift index 120d1ad7b..0e25fbd00 100644 --- a/DXFeedFrameworkTests/EndpointPublisherTest.swift +++ b/DXFeedFrameworkTests/EndpointPublisherTest.swift @@ -15,7 +15,7 @@ final class EndpointPublisherTest: XCTestCase { DXEndpointState.connecting: expectation(description: "Connecting")] let listener = TestListener(expectations: expectations) endpoint?.add(observer: listener) - try endpoint?.connect(":4700") + try endpoint?.connect(":4777") let exps = Array(expectations.filter({ element in element.key != .notConnected }).values) diff --git a/DXFeedFrameworkTests/EndpointTest.swift b/DXFeedFrameworkTests/EndpointTest.swift index cf9bc6c0e..c560c0e7d 100644 --- a/DXFeedFrameworkTests/EndpointTest.swift +++ b/DXFeedFrameworkTests/EndpointTest.swift @@ -52,11 +52,12 @@ final class EndpointTest: XCTestCase { DXEndpointState.notConnected: expectation(description: "NotConnected")] let listener = TestListener(expectations: expectations) endpoint?.add(observer: listener) - try endpoint?.connect(endpointAddress) let exps = Array(expectations.filter({ element in element.key != .notConnected }).values) + try endpoint?.connect(endpointAddress) wait(for: exps, timeout: 1) + try endpoint?.disconnect() let expsNotConnected = Array(expectations.filter({ element in element.key == .notConnected diff --git a/DXFeedFrameworkTests/Listeners/TestListener.swift b/DXFeedFrameworkTests/Listeners/TestListener.swift index 62500e1bf..4ad9f452d 100644 --- a/DXFeedFrameworkTests/Listeners/TestListener.swift +++ b/DXFeedFrameworkTests/Listeners/TestListener.swift @@ -9,16 +9,7 @@ import Foundation import XCTest @testable import DXFeedFramework -class TestListener: DXEndpointObserver, Hashable { - static func == (lhs: TestListener, rhs: TestListener) -> Bool { - return lhs.expectations == rhs.expectations - } - - func hash(into hasher: inout Hasher) { - hasher.combine(expectations) - } - - var state = DXEndpointState.notConnected +class TestListener: DXEndpointObserver { var expectations: [DXEndpointState: XCTestExpectation] init(expectations: [DXEndpointState: XCTestExpectation]) { self.expectations = expectations @@ -31,3 +22,13 @@ class TestListener: DXEndpointObserver, Hashable { } } } + +extension TestListener: Hashable { + static func == (lhs: TestListener, rhs: TestListener) -> Bool { + return lhs.expectations == rhs.expectations + } + + func hash(into hasher: inout Hasher) { + hasher.combine(expectations) + } +} diff --git a/Samples/PerfTestCL/ConnectEventListener.swift b/Samples/PerfTestCL/ConnectEventListener.swift index ccf353172..f701b1f6d 100644 --- a/Samples/PerfTestCL/ConnectEventListener.swift +++ b/Samples/PerfTestCL/ConnectEventListener.swift @@ -15,6 +15,3 @@ class ConnectEventListener: AbstractEventListener { } } } - - -