Skip to content

Commit e38a9cc

Browse files
author
Šimon Šesták
committed
refactor(concurrency): Refactor ObserverContextStore to use OSAllocatedUnfairLock
The ObserverContextStore was converted from an actor to a final class, utilizing OSAllocatedUnfairLock for thread-safe synchronous access. This simplifies the ObserverInterceptor by removing unnecessary Task wrappers and associated await calls. The minimum macOS deployment target was also updated to v13.
1 parent 2dde75d commit e38a9cc

3 files changed

Lines changed: 18 additions & 28 deletions

File tree

Sources/GraphQLAPIKit/Interceptors/ObserverInterceptor.swift

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,12 @@ struct ObserverInterceptor<Observer: GraphQLNetworkObserver>: ApolloInterceptor
3131
let requestId = urlRequest.hashValue.description
3232

3333
if response == nil {
34-
// BEFORE network fetch - call willSendRequest and store context
34+
// BEFORE network fetch - call willSendRequest and store context synchronously
3535
let context = observer.willSendRequest(urlRequest)
36-
Task {
37-
await contextStore.store(context, for: requestId)
38-
}
36+
contextStore.store(context, for: requestId)
3937
} else {
4038
// AFTER network fetch - retrieve context and call didReceiveResponse
41-
Task {
42-
guard let context = await contextStore.retrieve(for: requestId) else {
43-
return
44-
}
39+
if let context = contextStore.retrieve(for: requestId) {
4540
observer.didReceiveResponse(
4641
for: urlRequest,
4742
response: response?.httpResponse,
@@ -54,15 +49,9 @@ struct ObserverInterceptor<Observer: GraphQLNetworkObserver>: ApolloInterceptor
5449
// Wrap completion to handle errors
5550
let wrappedCompletion: (Result<GraphQLResult<Operation.Data>, Error>) -> Void = { result in
5651
if case .failure(let error) = result {
57-
Task {
58-
guard let context = await contextStore.retrieve(for: requestId) else {
59-
completion(result)
60-
return
61-
}
52+
if let context = contextStore.retrieve(for: requestId) {
6253
observer.didFail(request: urlRequest, error: error, context: context)
63-
completion(result)
6454
}
65-
return
6655
}
6756
completion(result)
6857
}
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import Foundation
2+
import os
23

3-
/// Actor that stores observer contexts keyed by request identifier.
4+
/// Thread-safe store for observer contexts keyed by request identifier.
45
/// Enables two interceptor instances to share state across the interceptor chain.
5-
actor ObserverContextStore<Context> {
6-
private var contexts: [String: Context] = [:]
6+
final class ObserverContextStore<Context: Sendable>: Sendable {
7+
private let state = OSAllocatedUnfairLock(initialState: [String: Context]())
78

89
func store(_ context: Context, for requestId: String) {
9-
contexts[requestId] = context
10+
state.withLock { $0[requestId] = context }
1011
}
1112

1213
func retrieve(for requestId: String) -> Context? {
13-
contexts.removeValue(forKey: requestId)
14+
state.withLock { $0.removeValue(forKey: requestId) }
1415
}
1516
}

Tests/GraphQLAPIKitTests/GraphQLNetworkObserverTests.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,25 +87,25 @@ final class GraphQLNetworkObserverTests: XCTestCase {
8787

8888
// MARK: - Context Store Tests
8989

90-
func testContextStoreOperations() async {
90+
func testContextStoreOperations() {
9191
let store = ObserverContextStore<String>()
9292

9393
// Test store and retrieve
94-
await store.store("context-1", for: "request-1")
95-
await store.store("context-2", for: "request-2")
96-
await store.store("context-3", for: "request-3")
94+
store.store("context-1", for: "request-1")
95+
store.store("context-2", for: "request-2")
96+
store.store("context-3", for: "request-3")
9797

9898
// Retrieve in different order
99-
let context2 = await store.retrieve(for: "request-2")
100-
let context1 = await store.retrieve(for: "request-1")
101-
let context3 = await store.retrieve(for: "request-3")
99+
let context2 = store.retrieve(for: "request-2")
100+
let context1 = store.retrieve(for: "request-1")
101+
let context3 = store.retrieve(for: "request-3")
102102

103103
XCTAssertEqual(context1, "context-1")
104104
XCTAssertEqual(context2, "context-2")
105105
XCTAssertEqual(context3, "context-3")
106106

107107
// Verify retrieve removes context
108-
let secondRetrieve = await store.retrieve(for: "request-1")
108+
let secondRetrieve = store.retrieve(for: "request-1")
109109
XCTAssertNil(secondRetrieve)
110110
}
111111
}

0 commit comments

Comments
 (0)