Subscriptions


Subscriptions are long-lived GraphQL read operations that can update their response over time, enabling clients to receive new data as it becomes available.

Apollo iOS supports subscriptions over:

You must use whichever protocol is supported by your GraphQL endpoint.

Enable WebSocket subscription support

The default NetworkTransport for Apollo iOS is RequestChainNetworkTransport, which supports subscriptions over HTTP with no additional configuration.

To use WebSocket subscriptions, you need to set up a WebSocketTransport and configure your ApolloClient to use it. The most common pattern routes subscriptions through WebSocket while sending queries and mutations over HTTP, using SplitNetworkTransport:

Swift
1import Apollo
2import ApolloWebSocket
3
4let store = ApolloStore()
5
6let httpTransport = RequestChainNetworkTransport(
7  interceptorProvider: DefaultInterceptorProvider(store: store),
8  endpointURL: URL(string: "https://example.com/graphql")!
9)
10
11let wsTransport = try WebSocketTransport(
12  urlSession: URLSession.shared,
13  store: store,
14  endpointURL: URL(string: "wss://example.com/graphql")!
15)
16
17let splitTransport = SplitNetworkTransport(
18  queryTransport: httpTransport,
19  mutationTransport: httpTransport,
20  subscriptionTransport: wsTransport
21)
22
23let client = ApolloClient(networkTransport: splitTransport, store: store)

For full WebSocket transport configuration options including authentication, auto-reconnection, pause/resume, and lifecycle events, see WebSocket Transport.

Generating and executing

Apollo iOS supports subscriptions via code generation. Similar to queries, subscriptions are represented by instances of generated classes, conforming to the GraphQLSubscription protocol.

GraphQL
ReviewAddedSubscription.graphql
1subscription ReviewAdded {
2  reviewAdded {
3    id
4    stars
5  }
6}

After you generate these classes, you can execute subscriptions using ApolloClient.subscribe(subscription:). Your client continues to receive updated data until the subscription is canceled or is terminated by the server.

Swift
1let subscription = try client.subscribe(subscription: ReviewAddedSubscription())
2
3Task {
4  for try await result in subscription {
5    // Handle each update from the subscription.
6  }
7}

Note: GraphQL subscriptions are distinct from watching queries. A query watcher is only updated when new data is written to the local cache (usually by another network operation). A GraphQL subscription is a long-lived request that might receive updated data from the server continually.

Subscription lifecycle state

The SubscriptionStream returned by subscribe exposes a state property that reflects the subscription's current position in its lifecycle, regardless of which transport is in use:

Swift
1let subscription = try client.subscribe(subscription: ReviewAddedSubscription())
2
3print(subscription.state) // .pending
4
5Task {
6  for try await result in subscription {
7    print(subscription.state) // .active
8    // handle result
9  }
10
11  print(subscription.state) // .finished(.completed)
12}

The possible states are:

StateDescription
.pendingInitiated but not yet active.
.activeActive and receiving data.
.reconnectingConnection lost; transport is reconnecting. WebSocket only.
.pausedConnection intentionally paused. WebSocket only.
.finished(.completed)Server completed the subscription normally.
.finished(.cancelled)Canceled by the client.
.finished(.error(error))Terminated due to an error.

The .reconnecting and .paused states are specific to WebSocketTransport. When using HTTP-based subscriptions via RequestChainNetworkTransport, a subscription moves through .pending.active.finished(…) only.

Canceling a subscription

Apollo iOS participates in Swift structured concurrency task cancellation for subscription cancellation. To cancel a subscription, cancel the Task that the subscription is being iterated in.

When you cancel the task, the subscription stream terminates gracefully and the operation is completed on the server.

In SwiftUI

The idiomatic SwiftUI approach is to use the .task {} view modifier. It automatically starts the subscription when the view appears and cancels it when the view disappears — no manual task management required.

Swift
1struct ReviewFeedView: View {
2  let client: ApolloClient
3  @State private var latestReview: ReviewAddedSubscription.Data.ReviewAdded?
4  @State private var error: Error?
5
6  var body: some View {
7    Group {
8      if let review = latestReview {
9        ReviewRow(review: review)
10      } else {
11        ProgressView("Waiting for reviews…")
12      }
13    }
14    .task {
15      do {
16        let subscription = try client.subscribe(subscription: ReviewAddedSubscription())
17        for try await result in subscription {
18          latestReview = result.data?.reviewAdded
19        }
20      } catch {
21        self.error = error
22      }
23    }
24  }
25}

For more complex views, extract subscription logic into an @Observable model and call it from .task {}:

Swift
1@Observable
2class ReviewFeedModel {
3  var latestReview: ReviewAddedSubscription.Data.ReviewAdded?
4  var error: Error?
5
6  func subscribe(using client: ApolloClient) async {
7    do {
8      let subscription = try client.subscribe(subscription: ReviewAddedSubscription())
9      for try await result in subscription {
10        latestReview = result.data?.reviewAdded
11      }
12    } catch {
13      self.error = error
14    }
15  }
16}
17
18struct ReviewFeedView: View {
19  let client: ApolloClient
20  @State private var model = ReviewFeedModel()
21
22  var body: some View {
23    ReviewContent(model: model)
24      .task {
25        await model.subscribe(using: client)
26      }
27  }
28}

The .task {} modifier cancels its task when the view is removed from the hierarchy. Because the subscription's for try await loop is running inside that task, the subscription is automatically canceled at the same time.

In UIKit

In UIKit, store a reference to the Task and cancel it when the view controller is deallocated:

Swift
1class ReviewViewController: UIViewController {
2
3  let client: ApolloClient!
4  private var subscriptionTask: Task<Void, Never>?
5
6  override func viewDidAppear(_ animated: Bool) {
7    super.viewDidAppear(animated)
8    subscriptionTask = Task {
9      do {
10        let subscription = try client.subscribe(subscription: ReviewAddedSubscription())
11        for try await result in subscription {
12          // Update UI on the main actor
13          updateUI(with: result.data)
14        }
15      } catch {
16        print("Subscription error: \(error)")
17      }
18    }
19  }
20
21  override func viewDidDisappear(_ animated: Bool) {
22    super.viewDidDisappear(animated)
23    subscriptionTask?.cancel()
24  }
25}
Feedback

Edit on GitHub

Ask Community