All skills
apollographql avatar

/apollo-ios

@e50044c official
by Apollo GraphQLapollographql/skills115 stars
13

Guide for building Apple-platform applications with Apollo iOS, the strongly-typed GraphQL client for Swift. Use this skill when: (1) adding Apollo iOS to a Swift Package Manager or Xcode project, (2) configuring `apollo-codegen-config.json` and running code generation, (3) configuring an `ApolloClient` with auth, interceptors, and caching, (4) writing queries, mutations, or subscriptions from SwiftUI views, (5) writing tests against generated operation mocks.

Use this Skill: https://skilld.dev/gh/apollographql/skills/apollo-ios

This session only. Nothing lands on disk.

referencessubscriptions.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Subscriptions

Apollo iOS supports GraphQL subscriptions over two transports:

  • HTTP multipart — subscriptions run over the same RequestChainNetworkTransport that handles queries and mutations, using the Apollo Router multipart subscription protocol. Nothing extra to configure — if your server supports multipart subscriptions, you're done.
  • WebSocket (graphql-transport-ws) — a persistent socket connection via ApolloWebSocket's WebSocketTransport. Can carry any operation type, not just subscriptions.

This reference covers both options, plus auth via connection params, backgrounding via pause() / resume(), and consuming a subscription from SwiftUI.

Pick a transport

Transport Use when
HTTP multipart (via RequestChainNetworkTransport) Your server supports the multipart subscription protocol (Apollo Router does). Simplest setup — no second transport to configure.
WebSocketTransport alone Every operation (query, mutation, subscription) runs over the WebSocket connection.
SplitNetworkTransport (HTTP + WebSocket) Queries and mutations go over HTTP, subscriptions go over the WebSocket. Common when the server exposes subscriptions only over graphql-transport-ws.

The rest of this reference focuses on the WebSocket setups because they have the most moving parts (connection lifecycle, backgrounding, auth via connection_init). For HTTP multipart subscriptions, there is no additional setup — call client.subscribe(subscription:) against an ApolloClient built with a standard RequestChainNetworkTransport (setup.md), and consume the returned SubscriptionStream exactly as shown in Consume a subscription from SwiftUI below.

Setup — SplitNetworkTransport (recommended)

import Apollo
import ApolloWebSocket
import Foundation

func makeApolloClient() throws -> ApolloClient {
  let store = ApolloStore()
  let endpointURL = URL(string: "https://api.example.com/graphql")!
  let webSocketURL = URL(string: "wss://api.example.com/graphql")!

  // HTTP transport for queries and mutations.
  let httpTransport = RequestChainNetworkTransport(
    urlSession: URLSession(configuration: .default),
    interceptorProvider: DefaultInterceptorProvider(),
    store: store,
    endpointURL: endpointURL
  )

  // WebSocket transport for subscriptions.
  let webSocketTransport = try WebSocketTransport(
    urlSession: URLSession(configuration: .default),
    store: store,
    endpointURL: webSocketURL,
    configuration: WebSocketTransport.Configuration(
      reconnectionInterval: 1.0,
      connectingPayload: [
        // Sent in the `connection_init` message. See "Auth" below.
        "Authorization": "Bearer \(currentAuthToken())"
      ],
      pingInterval: 20.0
    )
  )

  let splitTransport = SplitNetworkTransport(
    queryTransport: httpTransport,
    mutationTransport: httpTransport,
    subscriptionTransport: webSocketTransport,
    uploadTransport: httpTransport
  )

  return ApolloClient(networkTransport: splitTransport, store: store)
}

Auth via connection params

graphql-transport-ws requires that auth be sent in the connection_init message rather than as an HTTP header on the upgrade request. Pass a connectingPayload in the transport configuration:

WebSocketTransport.Configuration(
  connectingPayload: [
    "Authorization": "Bearer \(token)"
  ]
)

When the token rotates, call updateConnectingPayload(_:) on the transport. You'll usually do this from whatever owns the auth session:

await webSocketTransport.updateConnectingPayload([
  "Authorization": "Bearer \(newToken)"
])

Existing subscriptions stay open. The new payload is used on the next (re)connection.

Backgrounding — pause and resume

When the app moves to the background, pause the transport so the OS can release the WebSocket without dropping subscribers. When the app returns to the foreground, resume. Subscription streams remain alive across a pause/resume cycle.

import SwiftUI

struct RootView: View {
  @Environment(\.apolloClient) private var apolloClient
  @Environment(\.scenePhase) private var scenePhase
  let webSocketTransport: WebSocketTransport

  var body: some View {
    ContentView()
      .onChange(of: scenePhase) { _, newPhase in
        Task {
          switch newPhase {
          case .background, .inactive:
            await webSocketTransport.pause()
          case .active:
            await webSocketTransport.resume()
          @unknown default:
            break
          }
        }
      }
  }
}

Hold a reference to the WebSocketTransport (for example, in the App struct or a dependency container) so you can call pause() / resume() from scene-phase callbacks.

Consume a subscription from SwiftUI

client.subscribe(subscription:) returns a SubscriptionStream<GraphQLResponse<Subscription>>, which is an AsyncSequence. Use .task so the subscription cancels automatically when the view disappears.

import SwiftUI
import Apollo

@Observable
@MainActor
final class MessageViewModel {
  var messages: [MessageReceivedSubscription.Data.MessageReceived] = []

  private let apolloClient: ApolloClient

  init(apolloClient: ApolloClient) { self.apolloClient = apolloClient }

  func listen() async {
    do {
      let stream = try apolloClient.subscribe(
        subscription: MessageReceivedSubscription(),
        cachePolicy: .cacheThenNetwork
      )
      for try await response in stream {
        if let newMessage = response.data?.messageReceived {
          messages.append(newMessage)
        }
      }
    } catch is CancellationError {
      // Expected when the view goes away.
    } catch {
      print("Subscription failed: \(error)")
    }
  }
}

struct ChatView: View {
  @Environment(\.apolloClient) private var apolloClient
  @State private var viewModel: MessageViewModel?

  var body: some View {
    Group {
      if let viewModel {
        List(viewModel.messages, id: \.id) { message in
          Text(message.body)
        }
        .task { await viewModel.listen() }
      }
    }
    .onAppear {
      if viewModel == nil {
        viewModel = MessageViewModel(apolloClient: apolloClient)
      }
    }
  }
}

Subscription cache policies

CachePolicy.Subscription has two cases:

  • .cacheThenNetwork — emit cached matches first, then deliver live events.
  • .networkOnly — ignore the cache; deliver live events only.

Default is .cacheThenNetwork. Use .networkOnly when cached data is never meaningful for the subscription (for example, presence or typing indicators).

Ground rules

  • Hold a single WebSocketTransport for the lifetime of the app. Never create one per view.
  • Always call pause() on .background / .inactive and resume() on .active. Failing to pause drains the battery and can get the app throttled.
  • Use .task { for try await response in stream { … } } to consume the SubscriptionStream. Task cancellation ends the subscription cleanly.
  • Auth tokens go in connectingPayload, not as an HTTP Authorization header on the upgrade request — graphql-transport-ws uses the connection_init message.
  • When the token rotates, call updateConnectingPayload(_:) on the transport rather than tearing it down.
  • Never block on await webSocketTransport.pause() from inside body. Put the await inside a Task.

Source: SKILL.md on GitHub

1 warning16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides guidance for implementing Apollo iOS and correctly mitigates potential indirect prompt injection risks associated with fetching external GraphQL schemas and metadata. It utilizes official vendor resources from a trusted organization.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

Signed by skilld at e50044c. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 5 months ago
What it can do
Runs commands
metadata
{
  "author": "apollographql",
  "version": "1.0.0"
}
All 1 allowed tools
Bash(apollo-ios-cli:*) Bash(swift:*) Bash(xcodebuild:*) Bash(git:*) Read Write Edit Glob Grep WebFetch
Other metadata
compatibility
iOS 15+, macOS 12+, tvOS 15+, watchOS 8+, visionOS 1+. Swift 6.1+, Xcode 16+. SwiftUI apps using Swift Concurrency.
  • graphql
  • swift
  • ios
  • apollo-client
  • code-generation
  • swiftui
  • async-await
  • caching
  • interceptors
  • websocket

README badge

README badge for apollographql/skills/apollo-ios

Generates strongly-typed Swift code from GraphQL operations and schemas, then configures an async/await ApolloClient with caching, interceptors, and WebSocket subscriptions for iOS 15+ and macOS 12+. Used to add Apollo iOS to Xcode projects via Swift Package Manager, wire code generation into builds, and implement queries and mutations from SwiftUI views.

Generated from the current SKILL.md.

What Apple platforms and Swift versions does Apollo iOS support?
Apollo iOS requires iOS 15+, macOS 12+, tvOS 15+, watchOS 8+, visionOS 1+, Swift 6.1+, and Xcode 16+. It targets SwiftUI apps using Swift Concurrency.
How do I install Apollo iOS in my project?
Add Apollo iOS via Swift Package Manager and install the `apollo-ios-cli`. Link the appropriate product (`Apollo` for targets using `ApolloClient`, `ApolloAPI` for targets only consuming generated models) to each target.
Should I run codegen from an Xcode build phase?
No. Do not wire `apollo-ios-cli generate` into an Xcode Run Script build phase — it slows compile times measurably on every build. Regenerate manually or via a dedicated script alias, and commit generated Swift files to source control.
How do I handle authentication with Apollo iOS?
Implement auth in a single `GraphQLInterceptor` that attaches tokens via `request.additionalHeaders["Authorization"]`, detects 401 responses via `.mapErrors`, and triggers retry by throwing `RequestChain.Retry(request:)`. Always pair it with `MaxRetryInterceptor` as a safety cap.
When should I enable test mocks?
Generate test mocks lazily by flipping `output.testMocks` from `none` to `swiftPackage` (or `absolute`) only when writing the first test that needs `Mock<Type>`, then regenerate and link the mocks target to your test target.

Generated from the current SKILL.md. These answers refresh after source changes.