SubscriptionStream initializer public (#975): The SubscriptionStream initializer was inadvertently scoped to package access, preventing adopters from writing custom conformances to the public SubscriptionNetworkTransport protocol. The initializer is now public. Fixes #3637. Thank you to @ahou8 for raising the issue.\r\n in GraphQL descriptions generating invalid Swift comments (#961): GraphQL field descriptions containing \r\n (Windows CRLF) line endings caused codegen to emit invalid Swift — only the first line received the /// doc comment prefix and subsequent lines were emitted as uncommented text, breaking compilation. Fixes #3553. Thank you to @iPhoneNoobDeveloper for the contribution.JSONDecodingError.wrongType error. Reference resolution now handles array nesting at any depth. Fixes #3609. Thank you to @gurusekhar-ibm for raising the issue.deprecatedEnumCases was set to .exclude, the mock codegen incorrectly used the first enum case as the default value in convenience initializers — even if that case was deprecated and excluded from the generated enum, causing a compile error. Fixes #3634. Thank you to @cswelin for raising the issue.OneOf input object enum cases not marked indirect (#954): A @oneOf input object that directly references itself generated a Swift enum that failed to compile with Recursive enum is not marked 'indirect'. Self-referencing cases are now correctly marked indirect. Fixes #3633. Thank you to @ahou8 for raising the issue.graphql-transport-ws protocol for subscriptions, queries, and mutations. See the WebSocket Transport documentation for setup and usage details.markTypesNonisolated codegen option that emits nonisolated on all generated type declarations. This prevents compilation errors when SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor is enabled. The option defaults to true when the codegen tool is compiled with Swift 6.2+.URLSessionTaskDelegate into ApolloURLSession, enabling delegate callback handling. Thank you to @hammy-e for the contribution.compileGraphQLResult (#929): Resolved a Swift compiler crash triggered by async let stack management during code generation in AsyncParsableCommand contexts. Thank you to @m4p for the contribution.graphql-transport-ws protocol for subscriptions, queries, and mutations. See the WebSocket Transport documentation for setup and usage details.URLSessionTaskDelegate into ApolloURLSession, enabling delegate callback handling. Thank you to @hammy-e for the contribution.JSONRequest.useGetForQueries mutable (#897): The useGetForQueries property on JSONRequest is now a var, allowing interceptors to mutate the request directly instead of copying it. Thank you to @nevil for the contribution.X-Apollo-Operation-Name header to requests to satisfy Apollo Router's CSRF prevention requirements.objectType(forTypename:) lookup (#918): Replaced the generated switch statement in SchemaMetadata.objectType(forTypename:) with a static dictionary lookup, improving deserialization performance by ~75% for schemas with many types (4000+). Thank you to @erneestoc for the contribution.Dictionary initialization with duplicate keys (#884): Dictionary initialization using literal elements now uses a safer internal initializer to prevent crashes when duplicate keys are used. In the event that duplicate keys are present, the value associated with the first key will override all other values. Thank you to @adysart for raising the issue.AsyncReadWriteLock (#862): This fixes issue #3616. Thank you to @AlexPan1992 for raising the issue.URLRequest.cachePolicy on GET requests (#859): Thank you to @samjdurante for the contribution.AsyncReadWriteLock (#851): When under heavy loads, the AsyncReadWriteLock used by the ApolloStore could cause a deadlock. This is now resolved. Thank you to @marksvend for raising the issue..noResults error was thrown instead of the expected CancellationError. This is now resolved. Thank you to @3redrubies for raising the issue.cannotParseChunkData parsing error. This is now resolved. Thank you to @ecant for raising the issue.useGETForQueries was enabled. Thank you to @nevil for the contribution.__fulfilledFragments and collects selections from all of them, instead of just traversing downward from self.__selections. This ensures all expected data is collected from merged fragments. See PR #832. Thank you to @JOyo246 for raising the issue.SelectionSet equality with @include/@skip fields (#840): When a single field was used in a selection set with an @include/@skip directive, an assertionFailure would crash the program. This is now fixed.SPM CLI Installation (#795): Fixed CLI installation via Swift Package Manager by standardizing relative path handling.
SPI Attributes on Internal Models (#796): Fixed code generation to exclude @_spi attributes from models with internal access modifier, preventing compilation errors.
Apollo iOS 2.0 is now available! 🎉
Apollo iOS 2.0 represents a complete reimplementation of many components of the library, redesigned from the ground up to take full advantage of Swift's modern concurrency model. This major release introduces significant breaking changes but provides substantial improvements in type safety, performance, and developer experience.
async/await and structured concurrencySendable for improved thread safetyasync/await instead of callback-based APIscacheFirst, networkFirst, networkOnly, cacheOnly, cacheAndNetwork) with specific return signaturesGraphQLInterceptor, HTTPInterceptor, CacheInterceptor, and ResponseParsingInterceptor typesGraphQLResult with improved generic typing over operationsMinimum Deployment Targets
Removed Features
URLSessionClient replaced with ApolloURLSession protocol@TaskLocal values for dependency injectionCurrent Limitations
RequestChainNetworkTransport. Web socket support will be added in a future 2.0.x release.Apollo iOS 2.0 migration follows a two-phase approach:
Phase 1: Breaking Changes
Interceptors to new discrete typesNetworkTransport implementationsNormalizedCache implementationsPhase 2: Incremental Updates
async/await APIsRequestContext with @TaskLocal valuesFor detailed migration instructions, see the Apollo iOS 2.0 Migration Guide.
ApolloClient usage from callbacks to async/awaitAdded @fieldPolicy directive (#766, #749): The @fieldPolicy directive provides new functionality for configuring cache keys for fields with arguments. See the documentation for more information.
RootSelectionSet Convenience Initializer (#748): Added convenience initializer for RootSelectionSet from inline fragments for easier model construction.
Swift 6 Language Mode (#740): Enabled Swift 6 language mode with strict concurrency checking for improved thread safety and modern Swift patterns.
Xcode 26 Support (#756): Updated project configuration and build settings for Xcode 26 compatibility.
Subscription Over HTTP Support for RequestChainNetworkTransport (#759): Added subscription support to RequestChainNetworkTransport with HTTP chunked multipart response handling for GraphQL subscriptions over HTTP.
Fix Fetch Default Cache Policies (#777): Fixed default cache policy behavior for fetch operations to ensure consistent caching behavior across different operation types.
Narrowly Scoped SelectionSet Equality Checking (#757, #770): Fixed equality checking implementation for generated models in Apollo iOS 2.0 to ensure proper comparison of selection set data.
CLI Installation (#746): Fixed issues with Apollo iOS CLI installation and distribution.
Remove Cocoapods Support (#723): Apollo iOS 2.0 will no longer support Cocoapods. While support was carried over in the alpha releases, this has been officially removed in beta 1.
Only Input Variables use Int32 in generated models (#729): Generated models in alpha-2 generated Int as Int32 to more safely represent GraphQL spec compliant data. This made using the models much more cumbersome. Beta 1 changes this behavior so only input variables that may be sent to a GraphQL server use Int32.
Narrowly Scoped SelectionSet Equality (#736): Generated models now implement Equatable and Hashable using only the relevant fields for the model. This means that named fragments that do not have access to fields from the parent operation that fetched them will be equal if only the fields the fragment can access are equal.
InputObject (#730): Fixed a bug when generating a nullable list of nullable items on an InputObject.Int32The generated models will now represent Integers using Int32 instead of Int. The GraphQL spec specifically states that Int is a 32-bit integer, so this aligns us more correctly with the specification.
The impact of this change on the usability of the generated models is unclear at this time. We are seeking feedback on if this change is too much of an issue for users. While we would like to keep this new behavior, if user feedback is negative, this may be reverted or made into an optional behavior based on a codegen configuration option.
The codegen engine, apollo-ios-cli, pagination library, unit tests and other development projects have all been updated to Swift 6 with strict concurrency enabled.
This is the first preview release of Apollo iOS 2.0. This preview release contains APIs that are still in development and are subject to change prior to stable release.
This version is likely to contain bugs and some features are still limited. This preview is intended to allow interested users to test out the new APIs and provide feedback to help shape the final product.
Apollo iOS 2.0 reimagines many of the APIs to take full advantage of the new Swift concurrency model. This is a non-exhaustive list of the key changes:
ApolloClient & CachePolicyThe APIs of ApolloClient have changed significantly to use async/await. Rather than providing a resultHandler closure that may be called one or more times, separate APIs are defined depending on if an operation expects single/multiple responses. CachePolicy has been broken up into multiple types that will automatically force the function with the correct return signature.
// Single response
let response = try await client.fetch(query: query, cachePolicy: .cacheFirst)
let response = try await client.fetch(query: query, cachePolicy: .networkFirst)
let response = try await client.fetch(query: query, cachePolicy: .networkOnly)
// Single response with Optional return value
let response = try await client.fetch(query: query, cachePolicy: .cacheOnly)
// Multiple responses
// Returns an AsyncThrowingStream<GraphQLResponse<Query>, any Swift.Error>
let responses = try client.fetch(query: query, cachePolicy: .cacheAndNetwork)
Task {
for try await response in responses {
// Handle response
}
}
Subscriptions and operations that provide incremental data (via the @defer directive and in the future @stream), will always return an AsyncThrowingStream<GraphQLResponse<Query>, any Swift.Error> of responses unless using the .cacheOnly policy.
let responses = try client.fetch(query: deferQuery, cachePolicy: .cacheFirst) // -> AsyncThrowingStream
let responses = try client.fetch(query: deferQuery, cachePolicy: .networkFirst) // -> AsyncThrowingStream
let responses = try client.fetch(query: deferQuery, cachePolicy: .networkOnly) // -> AsyncThrowingStream
let responses = try client.fetch(query: deferQuery, cachePolicy: .cacheAndNetwork)
Task {
for try await response in responses {
// Handle response
}
}
let response = try await client.fetch(query: deferQuery, cachePolicy: .cacheOnly) // async throws -> GraphQLResponse<DeferQuery>?
The for try await response in responses loop will continue to run until the operation is complete. For subscriptions, this may be indefinite. For this reason, the returned stream should be consumed within a Task.
Sendable TypesIn order to support the new Swift concurrency model, most of the types in Apollo iOS have been made Sendable. In order to make these types Sendable, some limitations were necessary.
var properties have been converted to constant let properties. We don't believe this should prevent users from accessing any necessary functionality, but we are seeking feedback on the effect this change has on your usage.open classes have been changed to final classes or structs. This prevents subclassing types such as RequestChainNetworkTransport, InterceptorProvider, JSONRequest, and others. If you are currently subclassing these types, you will need to convert your existing subclasses to wrappers that wrap these types and passthrough calls to them instead.The RequestChain and interceptor framework has been completely reimagined. The new version supports async/await and provides the ability to interact with the request at each step within the chain more safely with more explicit APIs.
If you are providing your own custom InterceptorProvider with your own interceptors, you will need to modify your code to utilize these new APIs.
The singular ApolloInterceptor that was used to handle any step of the request chain has been broken up into discrete interceptor types for different portions of request execution. Additionally, requests are sent down the request chain pre-flight and then back up the chain post-flight, allowing each interceptors to interact with both the request and response in a type-safe way.
ApolloInterceptor has been separated into 4 different interceptor types.
GraphQLInterceptor
GraphQLRequest and GraphQLResponseHTTPInterceptor
URLRequestHTTPURLResponse (readonly) and mutate the actual raw response Data prior to parsingCacheInterceptor
GraphQLInterceptorsResponseParsingInterceptor
GraphQLResponseNetworkFetchInterceptor is no longer used, as the network fetch is managed by the ApolloURLSession. See the section on ApolloURLSession for more information.
Requests are now processed by the RequestChain using the following flow:
GraphQLInterceptors receive and may mutate RequestCacheInterceptor if necessary (based on cache policy)GraphQLRequest.toURLRequest() called to obtain URLRequestHTTPInterceptors receive and may mutate URLRequestApolloURLSession handles networking with URLRequestHTTPInterceptors receive stream of HTTPResponse objects for each chunk & may mutate raw chunk Data streamResponseParsingInterceptor receives HTTPResponse and parses data chunks into stream of GraphQLResponseGraphQLInterceptors receive and may mutate GraphQLResponse with parsed GraphQLResult and (possibly) cache records.CacheInterceptor if necessary (based on cache policy)GraphQLResponse emitted out to NetworkTransportGraphQLResponse and HTTPResponse separatedPreviously, there was a single GraphQLResponse which included the HTTPResponse and optionally the ParsedResult (if the parsing interceptor had been called already). Now, since different interceptors will be called pre/post parsing, we have separate types for these response objects.
ApolloErrorInterceptorThe ApolloErrorInterceptor protocol has been removed. Instead, any GraphQLInterceptor can handle errors using .mapErrors(). If any following interceptors, or the ApolloURLSession throw an error, the mapErrors closures will be called. You can then re-throw it; throw a different error; or trigger a retry by throwing a RequestChain.Retry error. If you would like to use a dedicated error handling interceptor, it is recommended to place it as the first interceptor returned by your provider to ensure all errors thrown by the chain are handled.
RequestChain.RetryInterceptors are no longer provided a reference to the RequestChain, so they cannot call RequestChain.retry(request:) directly. Instead, any interceptor may throw a RequestChain.Retry error that contains the request to kick-off the retry with. This error is caught internally by the RequestChain which initiates a retry.
The network fetch is now managed by an ApolloURLSession provided to the ApolloClient. For your convenience, Foundation.URLSession already conforms to the ApolloURLSession protocol. This allows you to provide your own URLSession and have complete control over the session's configuration and delegate.
You may alternatively provide any other object that conforms to ApolloURLSession, wrapping the URLSession or providing an entirely separate networking stack.
async FunctionsMany of the public protocols in Apollo iOS have been modified to use async functions. If you have custom implementations of these types, they will need to be modified to use async/await instead of resultHandler closures.
This includes ApolloStore, NormalizedCache, NetworkTransport, and all interceptor types.
@fieldPolicy directive. (#735): Added a new @fieldPolicy directive that can be used to configure cache keys for reading data from the cache prior to making a network request for an operation. Cache keys can also be configured programatically, for more information see documentation here.SQLiteError. (#755): Adding the raw sqlite result code as an associated value on enum cases of SQLiteError.RootSelectionSet convenience initializer from inline fragment. (#748): Added a convenience initializer to RootSelectionSet that allows it to be initialized directly from an InlineFragment, simplifying the conversion process between fragments and root entity types.dependentKeys in GraphQLResult public. (#758): Making dependentKeys public allows for implementing custom ApolloStoreSubscribers similar to GraphQLQueryWatcher. Without this it's extremely difficult to know when to react to cache changes for a query. _Thank you to @chrsmys for the contribution.clientVersion build delimiter. (#721): This changes the version/build delimiter from a - to a +. This now matches the semver specification of that additional metadata.requireNonOptionalMockFields flag to ApolloCodegenConfiguration.OutputOptions. (#669): Added new flag to codegen output options to allow having non-optional fields in the test mocks if desired. Thank you to @dwroth for the contribution.DatabaseRow. (#664): Not having a public initializer on DatabasRow was hindering the ability to create custom SQLiteDatabase implementations. This solves that by adding a public initializer to DatabaseRow.Thank you to @ChrisLaganiere for the contribution.@_disfavoredOverload to the deprecated initialized in ApolloCodegenConfiguration to prevent possible warnings caused by the compiler selecting a deprecated initializer versus the new/current initializer. See PR #682. Thank you to @CraigSiemens for raising the issue.ReadTransaction (#661): Some users have use cases for accessing a custom NormalizedCache implementation directly while performing cache transactions. A new ReadOnlyNormalizedCache protocol exposes the cache as read-only in the ReadTransaction and as writable in the ReadWriteTransaction. See PR #661.all field merging causes selection set initializers to stop being generated for local cache mutations (#3554): Codegen will now force field merging behaviour and selection set initializer generation for local cache mutations. See PR #654.extensions key of each request. This Enhanced Client Awareness metric is collected in GraphOS along with the existing Client Awareness and general operation metrics.SQLite.swift and replaced it with direct interaction with the SQLite C API.subscriptions property inside of WebSocketTransport. Thank you to @tahirmt for the contribution.wrongType error if stored and read from the cache. This refactors the execution logic to correctly handle values from cache references in lists. See PR #637.mutateIfFulfilled function was added to facilitate that workflow while still preventing a fragment from being added or removed from an existing model. See PR #608.URLRequest timeout interval (#3522): Added a request context specialization protocol (RequestContextTimeoutConfigurable) that specifies options for configuring the timeout interval of a URLRequest. See PR #618.Object types that are generated so that only types that are referenced in an operation document or have a @typePolicy will be generated. See PR #601.InputObject needed a GraphQLNullable-specific subscript to prevent nil value keys being forcefully unwrapped. See PR #596. Thank you to @pixelmatrix for raising the issue.WebSocketTransport due to data races (#3512): This data race would occur if starting or stopping a subscription at the same time as a message received on the websocket. To prevent these data races the subscribers property is now an @Atomic property. See PR #599. Thank you to @tahirmt for the contribution.appendSchemaTypeFilenameSuffix) to add a suffix to schema generated filenames and prevent the build error. See PR #580.typePolicy directive (#554): The @typePolicy directive lets you specify an object's cache ID using key fields of the response object. See the documentation for full details. Thank you to @x-sheep for the contribution.Identifiable conformance on SelectionSet (#584): If the @typePolicy of a type uses a keyField of id the selection set will emit conformance to Swifts Identifiable protocol. Thank you to @x-sheep for the contribution.EntitySelectionSet while building operations. See PR #571.embeddedInTarget and other module types. See PR #581.DataDict initialization of deferredFragments for named fragments (#587): When deferred fragments are named fragments the deferred type should be the fragment generated definition name.