Skip to content

Merge connect 1.18.0 to connect #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ApolloTestSupport.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ Pod::Spec.new do |s|
s.visionos.deployment_target = '1.0'

s.source_files = 'Sources/ApolloTestSupport/*.swift'
s.dependency 'Apollo', '= ' + version
s.dependency 'Apollo/Core', '= ' + version

end
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
# Change Log

## v1.18.0

### New
- **Reduce Generated Schema Types ([#3505](https://github.com/apollographql/apollo-ios/issues/3505)):** Adds a new codegen configuration option to reduce the number of `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](https://github.com/apollographql/apollo-ios-dev/pull/601).

### Improvement
- **Identifiable conformance for named fragments ([#595](https://github.com/apollographql/apollo-ios-dev/pull/595)):** Identifiable conformance was previously implemented ([#584](https://github.com/apollographql/apollo-ios-dev/pull/584)) for selection sets and has now been extended to include named fragments. _Thank you to [@x-sheep](https://github.com/x-sheep) for the contribution._

### Fixed
- **Accessing an unset deprecated field in input causes a crash ([#3506](https://github.com/apollographql/apollo-ios/issues/3506)):** `InputObject` needed a `GraphQLNullable`-specific subscript to prevent nil value keys being forcefully unwrapped. See PR [#596](https://github.com/apollographql/apollo-ios-dev/pull/596). _Thank you to [@pixelmatrix](https://github.com/pixelmatrix) for raising the issue._
- **Crash in `WebSocketTransport` due to data races ([#3512](https://github.com/apollographql/apollo-ios/issues/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](https://github.com/apollographql/apollo-ios-dev/pull/599). _Thank you to [@tahirmt](https://github.com/tahirmt) for the contribution._

## v1.17.0

### New
- **Add suffix to schema type filenames ([#2598](https://github.com/apollographql/apollo-ios/issues/2598)):** When fragments were named the same as schema types code generation would produce two files with the same name, but at different paths, for each respective type. This would cause a build error in Xcode. There is a new codegen configuration option (`appendSchemaTypeFilenameSuffix`) to add a suffix to schema generated filenames and prevent the build error. See PR [#580](https://github.com/apollographql/apollo-ios-dev/pull/580).
- **Specify caching fields with `typePolicy` directive ([#554](https://github.com/apollographql/apollo-ios-dev/pull/554)):** The `@typePolicy` directive lets you specify an object's cache ID using key fields of the response object. See the [documentation](https://www.apollographql.com/docs/ios/caching/cache-key-resolution#the-typepolicy-directive) for full details. _Thank you to [@x-sheep](https://github.com/x-sheep) for the contribution._
- **Emit `Identifiable` conformance on `SelectionSet` ([#584](https://github.com/apollographql/apollo-ios-dev/pull/584)):** If the `@typePolicy` of a type uses a `keyField` of `id` the selection set will emit conformance to Swifts [`Identifiable` protocol](https://developer.apple.com/documentation/swift/identifiable). _Thank you to [@x-sheep](https://github.com/x-sheep) for the contribution._

### Improvement
- **Improved performance of code generation on operations with many nested fragments ([#3434](https://github.com/apollographql/apollo-ios/issues/3434)):** When fragment field merging is disabled the fragment selection trees are no longer merged into the `EntitySelectionSet` while building operations. See PR [#571](https://github.com/apollographql/apollo-ios-dev/pull/571).

### Fixed
- **Defer metadata extension ([#3505](https://github.com/apollographql/apollo-ios/issues/3503)):** Metadata extensions for deferred selection sets were incorrectly generated inside the namespace extension for `embeddedInTarget` and `other` module types. See PR [#581](https://github.com/apollographql/apollo-ios-dev/pull/581).
- **`DataDict` initialization of `deferredFragments` for named fragments ([#587](https://github.com/apollographql/apollo-ios-dev/pull/587)):** When deferred fragments are named fragments the deferred type should be the fragment generated definition name.

## v1.16.1

### Fixed
- **Web socket data race crash fixed ([#578](https://github.com/apollographql/apollo-ios-dev/pull/578)):** A data race in the web socket layer was causing crashes in some rare circumstances.

Expand Down
Binary file modified CLI/apollo-ios-cli.tar.gz
Binary file not shown.
20 changes: 12 additions & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 🔮 Apollo iOS Roadmap

**Last updated: 2025-01-21**
**Last updated: 2025-02-18**

For up to date release notes, refer to the project's [Changelog](https://github.com/apollographql/apollo-ios/blob/main/CHANGELOG.md).

Expand All @@ -11,6 +11,9 @@ For up to date release notes, refer to the project's [Changelog](https://github.
- Please report feature requests or bugs as a new [issue](https://github.com/apollographql/apollo-ios/issues/new/choose).
- If you already see an issue that interests you please add a 👍 or a comment so we can measure community interest.

### [Currently Requesting Feedback on Caching](https://github.com/apollographql/apollo-ios/issues/3501)
We are currently looking for feedback on what features, use cases, or improvements you would like to see supported by the next iteration of the Apollo iOS normalized cache. Please provide your input on [this issue](https://github.com/apollographql/apollo-ios/issues/3501).

---

## [1.x.x patch releases](https://github.com/apollographql/apollo-ios/milestone/70)
Expand Down Expand Up @@ -40,13 +43,6 @@ _Status: In design phase. Current RFC for design is available [here](https://git
- ✅ [`ExistentialAny` upcoming feature](https://github.com/apollographql/apollo-ios/issues/3205)
- (in progress) [`Sendable` types and `async/await` APIs](https://github.com/apollographql/apollo-ios/issues/3291)

### [Reduce generated schema types](https://github.com/apollographql/apollo-ios/milestone/71)

_Status: API Design in progress_

- Right now we are naively generating schema types that we don't always need. A smarter algorithm can reduce generated code for certain large schemas that are currently having every type in their schema generated
- Create configuration for manually indicating schema types you would like to have schema types and TestMocks generated for

### [Support codegen of operations without response models](https://github.com/apollographql/apollo-ios/issues/3165)

_Status: API Design in progress_
Expand All @@ -62,6 +58,12 @@ _Status: Not started_
- Provide a mechanism for making generated reponse models mutable.
- This will allow mutability on an opt-in basis per selection set or definition.

### `@fieldPolicy` directive

_Status: Not Started_

The [`@fieldPolicy` directive](https://www.apollographql.com/docs/kotlin/caching/declarative-ids#fieldpolicy) is currently supported by Apollo Kotlin and Apollo Web. This directive allows users to configure field arguments to be used to retrieve data stored in the normalized cache. In our efforts to improve feature parity across the client platforms, we plan to implement this directive in Apollo iOS as well.

### Semantic Nullability

_Status: Feature Design_
Expand All @@ -80,6 +82,8 @@ These are the initiatives planned for future major version releases:

## Caching

[**Requesting Feedback**](https://github.com/apollographql/apollo-ios/issues/3501): We are currently looking for feedback on what features, use cases, or improvements you would like to see supported by the next iteration of the Apollo iOS normalized cache. Please provide your input on [this issue](https://github.com/apollographql/apollo-ios/issues/3501).

- **Cache Improvements**: Here we are looking at bringing across some features inspired by Apollo Client 3 and Apollo Kotlin
- Better pagination support. Better support for caching and updating paginated lists of objects.
- Result model improvements
Expand Down
2 changes: 1 addition & 1 deletion Sources/Apollo/Constants.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Foundation

public enum Constants {
public static let ApolloVersion: String = "1.16.1"
public static let ApolloVersion: String = "1.18.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ struct CacheDataExecutionSource: GraphQLExecutionSource {
return transaction.loadObject(forKey: reference.key)
}

func computeCacheKey(for object: Record, in schema: any SchemaMetadata.Type) -> CacheKey? {
func computeCacheKey(
for object: Record,
in schema: any SchemaMetadata.Type,
inferredToImplementInterface interface: Interface?
) -> CacheKey? {
return object.key
}

Expand Down
18 changes: 15 additions & 3 deletions Sources/Apollo/GraphQLExecutionSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,18 @@ public protocol GraphQLExecutionSource {
/// - Parameters:
/// - object: The data for the object from the source.
/// - schema: The schema that the type the object data represents belongs to.
/// - implementedInterface: An optional ``Interface`` that the object is
/// inferred to implement. If the cache key is being resolved for a selection set with an
/// interface as it's `__parentType`, you can infer the object must implement that interface.
/// You should provide that interface to this parameter.
/// - Returns: A cache key for normalizing the object in the cache. If `nil` is returned the
/// object is assumed to be stored in the cache with no normalization. The executor will
/// construct a cache key based on the object's path in its enclosing operation.
func computeCacheKey(for object: RawObjectData, in schema: any SchemaMetadata.Type) -> CacheKey?
func computeCacheKey(
for object: RawObjectData,
in schema: any SchemaMetadata.Type,
inferredToImplementInterface implementedInterface: Interface?
) -> CacheKey?
}

/// A type of `GraphQLExecutionSource` that uses the user defined cache key computation
Expand All @@ -57,8 +65,12 @@ public protocol CacheKeyComputingExecutionSource: GraphQLExecutionSource {
}

extension CacheKeyComputingExecutionSource {
@_spi(Execution) public func computeCacheKey(for object: RawObjectData, in schema: any SchemaMetadata.Type) -> CacheKey? {
@_spi(Execution) public func computeCacheKey(
for object: RawObjectData,
in schema: any SchemaMetadata.Type,
inferredToImplementInterface implementedInterface: Interface?
) -> CacheKey? {
let dataWrapper = opaqueObjectDataWrapper(for: object)
return schema.cacheKey(for: dataWrapper)
return schema.cacheKey(for: dataWrapper, inferredToImplementInterface: implementedInterface)
}
}
8 changes: 7 additions & 1 deletion Sources/Apollo/GraphQLExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -493,9 +493,15 @@ public final class GraphQLExecutor<Source: GraphQLExecutionSource> {
onChildObject object: Source.RawObjectData,
accumulator: Accumulator
) -> PossiblyDeferred<Accumulator.PartialResult> {
let expectedInterface = rootSelectionSetType.__parentType as? Interface

let (childExecutionInfo, selections) = fieldInfo.computeChildExecutionData(
withRootType: rootSelectionSetType,
cacheKey: executionSource.computeCacheKey(for: object, in: fieldInfo.parentInfo.schema)
cacheKey: executionSource.computeCacheKey(
for: object,
in: fieldInfo.parentInfo.schema,
inferredToImplementInterface: expectedInterface
)
)

return execute(
Expand Down
55 changes: 45 additions & 10 deletions Sources/ApolloAPI/SchemaMetadata.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Foundation

/// A protocol that a generated GraphQL schema should conform to.
///
/// The generated schema metadata is the source of information about the generated types in the
Expand Down Expand Up @@ -43,20 +45,53 @@ extension SchemaMetadata {
/// Resolves the cache key for an object in a GraphQL response to be used by
/// `NormalizedCache` mechanisms.
///
/// Maps the type of the `object` using the ``graphQLType(for:)`` function, then gets the
/// ``CacheKeyInfo`` for the `object` using the ``SchemaConfiguration/cacheKeyInfo(for:object:)``
/// function.
/// Finally, this function transforms the ``CacheKeyInfo`` into the correct ``CacheReference``
/// for the `NormalizedCache`.
/// The algorithm for resolving the objects cache key:
/// 1. Map the type of the `object` using the ``graphQLType(for:)`` function.
/// 2. Attempt to gets the `CacheKeyInfo`` using programmatic cache key configuration.
/// 2a. Call the ``SchemaConfiguration/cacheKeyInfo(for:object:)`` function.
/// 2b. If `CacheKeyInfo` is found, transforms the ``CacheKeyInfo`` into the correct ``CacheReference``
/// for the `NormalizedCache` and return it.
/// 3. If no programmatic cache key is returned, attempt to resolve the `keyFields` for the object
/// 3a. Check if the object's type has `keyFields`.
/// 3b. If the type of the object is unknown (ie. it cannot be found by ``graphQLType(for:)``),
/// or the type does not have `keyFields`, check if the inferred interface for the type has
/// `keyFields`.
/// 3c. If `keyFields` are found, resolve the cache key by escaping and joining the values of
/// the `keyFields` on the object. Return the resolved cache key.
/// 4. If a cache key is not resolved programmatically or using `keyFields`, return `nil`.
///
/// - Parameter object: A ``JSONObject`` dictionary representing an object in a GraphQL response.
/// - Parameter implementedInterface: An optional ``Interface`` that the object is
/// inferred to implement. If the cache key is being resolved for a selection set with an
/// interface as it's `__parentType`, you can infer the object must implement that interface.
/// You should provide that interface to this parameter.
///
/// - Returns: A `String` representing the cache key for the `object` to be used by
/// `NormalizedCache` mechanisms.
@inlinable public static func cacheKey(for object: ObjectData) -> String? {
guard let type = graphQLType(for: object),
let info = configuration.cacheKeyInfo(for: type, object: object) else {
return nil
@inlinable public static func cacheKey(
for object: ObjectData,
inferredToImplementInterface implementedInterface: Interface? = nil
) -> String? {
guard let type = graphQLType(for: object) else { return nil }

if let info = configuration.cacheKeyInfo(for: type, object: object) {
return "\(info.uniqueKeyGroup ?? type.typename):\(info.id)"
}

guard let keyFields = type.keyFields ?? implementedInterface?.keyFields else { return nil }

let idValues = try? keyFields.map {
guard let keyFieldValue = object[$0] else {
throw JSONDecodingError.missingValue
}
let item = try String(_jsonValue: keyFieldValue._asAnyHashable)

// Escape all instances of `+` with a backslash, as well as other backslashes
return item.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "+", with: "\\+")
}
return "\(info.uniqueKeyGroup ?? type.typename):\(info.id)"

guard let id = idValues?.joined(separator: "+") else { return nil }
return "\(type.typename):\(id)"
}
}
12 changes: 12 additions & 0 deletions Sources/ApolloAPI/SchemaTypes/InputObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,23 @@ public struct InputDict: GraphQLOperationVariableValue, Hashable {

public var _jsonEncodableValue: (any JSONEncodable)? { data._jsonEncodableObject }

@_disfavoredOverload
public subscript<T: GraphQLOperationVariableValue>(key: String) -> T {
get { data[key] as! T }
set { data[key] = newValue }
}

public subscript<T: GraphQLOperationVariableValue>(key: String) -> GraphQLNullable<T> {
get {
if let value = data[key] {
return value as! GraphQLNullable<T>
}

return .none
}
set { data[key] = newValue }
}

public static func == (lhs: InputDict, rhs: InputDict) -> Bool {
lhs.data._jsonEncodableValue?._jsonValue == rhs.data._jsonEncodableValue?._jsonValue
}
Expand Down
20 changes: 19 additions & 1 deletion Sources/ApolloAPI/SchemaTypes/Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@
public struct Interface: Hashable, Sendable {
/// The name of the ``Interface`` in the GraphQL schema.
public let name: String

/// A list of fields used to uniquely identify an instance of an object implementing this interface.
///
/// This is set by adding a `@typePolicy` directive to the schema.
public let keyFields: [String]?

/// A list of name for Objects that implement this Interface
public let implementingObjects: [String]

/// Designated Initializer
///
/// - Parameter name: The name of the ``Interface`` in the GraphQL schema.
public init(name: String) {
public init(
name: String,
keyFields: [String]? = nil,
implementingObjects: [String]
) {
self.name = name
if keyFields?.isEmpty == false {
self.keyFields = keyFields
} else {
self.keyFields = nil
}
self.implementingObjects = implementingObjects
}
}
25 changes: 21 additions & 4 deletions Sources/ApolloAPI/SchemaTypes/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,45 @@ public struct Object: Hashable, Sendable {
/// - Parameters:
/// - typename: The name of the type.
/// - implementedInterfaces: A list of the interfaces implemented by the type.
/// - keyFields: A list of field names that are used to uniquely identify an instance of this type.
public init(
typename: String,
implementedInterfaces: [Interface]
implementedInterfaces: [Interface],
keyFields: [String]? = nil
) {
self.typename = typename
self.implementedInterfaces = implementedInterfaces
self._implementedInterfaces = implementedInterfaces
if keyFields?.isEmpty == false {
self.keyFields = keyFields
} else {
self.keyFields = nil
}
}

private let _implementedInterfaces: [Interface]

/// A list of the interfaces implemented by the type.
public let implementedInterfaces: [Interface]
@available(*, deprecated, message: "This property will be removed in version 2.0. To check if an Object implements an interface please use the 'implements(_)' function.")
public var implementedInterfaces: [Interface] {
return _implementedInterfaces
}

/// The name of the type.
///
/// When an entity of the type is included in a GraphQL response its `__typename` field will
/// match this value.
public let typename: String

/// A list of fields used to uniquely identify an instance of this object.
///
/// This is set by adding a `@typePolicy` directive to the schema.
public let keyFields: [String]?

/// A helper function to determine if the receiver implements a given ``Interface`` Type.
///
/// - Parameter interface: An ``Interface`` Type
/// - Returns: A `Bool` indicating if the receiver implements the given ``Interface`` Type.
public func implements(_ interface: Interface) -> Bool {
implementedInterfaces.contains(where: { $0 == interface })
interface.implementingObjects.contains(typename)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ class FoundationStream : NSObject, WebSocketStream, StreamDelegate, SOCKSProxyab
let proxyDict = CFNetworkCopySystemProxySettings()
let socksConfig = CFDictionaryCreateMutableCopy(nil, 0, proxyDict!.takeRetainedValue())
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertySOCKSProxy)
CFWriteStreamSetProperty(outputStream, propertyKey, socksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, socksConfig)
let dict = socksConfig as? [String: Any]
if let ip = dict?["HTTPSProxy"] as? String, let port = dict?["HTTPSPort"] as? Int {
let customSocksConfig = ["SOCKSProxy": ip, "SOCKSPort": port + 1, "SOCKSEnable": 1] as CFDictionary?
CFWriteStreamSetProperty(outputStream, propertyKey, customSocksConfig)
CFReadStreamSetProperty(inputStream, propertyKey, customSocksConfig)
}
}
#endif

Expand Down
Loading