Skip to content

Commit 4b49977

Browse files
Merge pull request #618 from apollographql/add/docs-on-a-plane
Update documentation to show how to use new delegates and cache policy
2 parents b15733e + 40b7724 commit 4b49977

5 files changed

Lines changed: 179 additions & 18 deletions

File tree

Sources/Apollo/HTTPNetworkTransport.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ public class HTTPNetworkTransport: NetworkTransport {
8282
/// - configuration: A session configuration used to configure the session. Defaults to `URLSessionConfiguration.default`.
8383
/// - sendOperationIdentifiers: Whether to send operation identifiers rather than full operation text, for use with servers that support query persistence. Defaults to false.
8484
/// - useGETForQueries: If query operation should be sent using GET instead of POST. Defaults to false.
85-
/// - preflightDelegate: A delegate to check with before sending a request.
86-
/// - requestCompletionDelegate: A delegate to notify when the URLSessionTask has completed.
85+
/// - delegate: [Optional] A delegate which can conform to any or all of `HTTPNetworkTransportPreflightDelegate`, `HTTPNetworkTransportTaskCompletedDelegate`, and `HTTPNetworkTransportRetryDelegate`. Defaults to nil.
8786
public init(url: URL,
8887
configuration: URLSessionConfiguration = .default,
8988
sendOperationIdentifiers: Bool = false,

docs/source/api-reference.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ title: API Reference
33
description: ''
44
---
55

6-
Please see [here](http://cocoadocs.org/docsets/Apollo/).
6+
[Coming soon!]

docs/source/fetching-queries.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,19 @@ As explained in more detail in [the section on watching queries](/watching-queri
103103

104104
`fetch(query:)` takes an optional `cachePolicy` that allows you to specify when results should be fetched from the server, and when data should be loaded from the local cache.
105105

106-
The default cache policy is `.returnCacheDataElseFetch`, which means data will be loaded from the cache when available, and fetched from the server otherwise. You can specify `.fetchIgnoringCacheData` to always fetch from the server, or `.returnCacheDataDontFetch` to returns data from the cache and never fetch from the server (it returns `nil` when cached data is not available).
106+
The default cache policy is `.returnCacheDataElseFetch`, which means data will be loaded from the cache when available, and fetched from the server otherwise.
107+
108+
Other cache polices which you can specify are:
109+
110+
- **`.fetchIgnoringCacheData`** to always fetch from the server, but still store results to the cache.
111+
- **`.fetchIgnoringCacheCompletely`** to always fetch from the server and not store results from the cache. If you're not using the cache at all, this method is preferred to `fetchIgnoringCacheData` for performance reasons.
112+
- **`.returnCacheDataDontFetch`** to return data from the cache and never fetch from the server. This policy will return `nil` when cached data is not available.
113+
- **`.returnCacheDataAndFetch`** to return cached data immediately, then perform a fetch to see if there are any updates. This is mostly useful if you're watching queries, since those will be updated when the call to the server returns.
114+
115+
## Using `GET` instead of `POST` for queries
116+
117+
By default, Apollo constructs queries and sends them to your graphql endpoint using `POST` with the JSON generated.
118+
119+
If you want Apollo to use `GET` instead, pass `true` to the optional `useGETForQueries` parameter when setting up your `HTTPNetworkTransport`. This will set up all queries conforming to `GraphQLQuery` sent through the HTTP transport to use `GET`.
120+
121+
>Please note that this is a toggle which affects all queries sent through that client, so if you need to have certain queries go as `POST` and certain ones go as `GET`, you will likely have to swap out the `HTTPNetworkTransport`.

docs/source/initialization.md

Lines changed: 160 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,173 @@
22
title: Creating a client
33
---
44

5-
In most cases, you'll want to create a single shared instance of `ApolloClient` and point it at your GraphQL server. The easiest way to do this is to define a global variable in `AppDelegate.swift`:
5+
## Basic Client Creation
6+
7+
In most cases, you'll want to create a single shared instance of `ApolloClient` and point it at your GraphQL server. The easiest way to do this is to create a singleton:
68

79
```swift
8-
let apollo = ApolloClient(url: URL(string: "http://localhost:8080/graphql")!)
10+
class Apollo {
11+
static let shared = Apollo()
12+
13+
private(set) lazy var client = ApolloClient(url: URL(string: "http://localhost:8080/graphql")!)
14+
}
915
```
1016

11-
## Adding additional headers
17+
Under the hood, this will create a client using `HTTPNetworkTransport` with a default configuration. You can then use this client from anywhere in your code with `Apollo.shared.client`.
18+
19+
## Advanced Client Creation
1220

13-
If you need to add additional headers to requests, to include authentication details for example, you can create your own `URLSessionConfiguration` and use this to configure an `HTTPNetworkTransport`. If you want to define the client as a global variable, you can use an immediately invoked closure here:
21+
For more advanced usage of the client, you can use this initializer which allows you to pass in an object conforming to the `NetworkTransport` protocol, as well as a store if you wish:
1422

1523
```swift
16-
let apollo: ApolloClient = {
17-
let configuration = URLSessionConfiguration.default
18-
// Add additional headers as needed
19-
configuration.httpAdditionalHeaders = ["Authorization": "Bearer <token>"] // Replace `<token>`
24+
public init(networkTransport: NetworkTransport,
25+
store: ApolloStore = ApolloStore(cache: InMemoryNormalizedCache()))
26+
```
2027

21-
let url = URL(string: "http://localhost:8080/graphql")!
28+
The available implementations are:
2229

23-
return ApolloClient(networkTransport: HTTPNetworkTransport(url: url, configuration: configuration))
24-
}()
25-
```
30+
- **`HTTPNetworkTransport`**, which has a number of configurable options and uses standard HTTP requests to communicate with the server
31+
- **`WebSocketTransport`**, which will send everything using a web socket. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this.
32+
- **`SplitNetworkTransport`**, which will send subscription operations via a web socket and all other operations via HTTP. If you're using CocoaPods, make sure to install the `Apollo/WebSocket` sub-spec to access this.
33+
34+
### Using `HTTPNetworkTransport`
35+
36+
The initializer for `HTTPNetworkTransport` has several properties which can allow you to get better information and finer-grained control of your HTTP requests and responses:
37+
38+
- `configuration` allows you to pass in a custom `URLSessionConfiguration` to set up anything which needs to be done for every single request without alteration. This defaults to `URLSessionConfiguration.default`.
39+
- `sendOperationIdentifiers` allows you send operation identifiers along with your requests. **NOTE:** To send operation identifiers, Apollo types must be generated with `operationIdentifier`s or sending data will crash. Due to this restriction, this option defaults to `false`.
40+
- `useGETForQueries` sends all requests of `query` type using `GET` instead of `POST`. This defaults to `false` to preserve existing behavior in older versions of the client.
41+
- `delegate` Can conform to one or many of several sub-protocols for `HTTPNetworkTransportDelegate`, detailed below.
42+
43+
### Using `HTTPNetworkTransportDelegate`
44+
45+
This delegate includes several sub-protocols so that a single parameter can be passed no matter how many sub-protocols it conforms to.
46+
47+
If you conform to a particular sub-protocol, you must implement all the methods in that sub-protocol, but we've tried to break things out in a sensible fashion. The sub-protocols are:
48+
49+
#### `HTTPNetworkTransportPreflightDelegate`
50+
51+
This protocol allows pre-flight validation of requests, the ability to bail out before modifying the request, and the ability to modify the `URLRequest` with things like additional headers.
52+
53+
The `shouldSend` method is called before any modifications are made by `willSend`. This allows you do things like check that you have an authentication token in your keychain, and if not, prevent the request from hitting the network. When you cancel a request in `shouldSend`, you will receive an error indicating the request was cancelled.
54+
55+
The `willSend` method is called with an `inout` parameter for the `URLRequest` which is about to be sent. There are several uses for this functionality.
56+
57+
The first is simple logging of the request that's about to go out. You could theoretically do this in `shouldSend`, but particularly if you're making any changes to the request, you'd probably want to do your logging after you've finished those changes.
58+
59+
The most common usage is to modify the request headers. Note that when modifying request headers, you'll need to make a copy of any pre-existing headers before adding new ones. See the [Example Advanced Client Setup](#example-advanced-client-setup) for details.
60+
61+
You can also make any other changes you need to the request, but be aware that going too crazy with this may lead to Unexpected Behavior™.
62+
63+
#### `HTTPNetworkTransportTaskCompletedDelegate`
64+
65+
This delegate allows you to peer in to the raw data returned to the `URLSession`. This is helpful both for logging what you're getting directly from your server and for grabbing any information out of the raw response, such as updated authentication tokens, which would be removed before parsing is completed.
66+
67+
#### `HTTPNetworkTransportRetryDelegate`
68+
69+
This delegate allows you to asynchronously determine whether to retry your request. This is asynchronous to allow for things like re-authenticating your user.
70+
71+
When you decide to retry, the `send` operation for your `GraphQLOperation` will be retried. This means you'll get brand new callbacks from `HTTPNetworkTransportPreflightDelegate` to update your headers again as if it was a totally new request. Therefore, the parameter for the completion closure is a simple `true`/`false` option: Pass `true` to retry, pass `false` to error out.
72+
73+
**IMPORTANT**: Do not call `true` blindly in the completion closure. If your server is returning 500s or if the user has no internet, this will create an infinite loop of requests that are retrying. This **will** kill your user's battery, and might also run up the bill on their data plan. Make sure to only request a retry when there's something your code can actually do about the problem!
74+
75+
### Example Advanced Client Setup
76+
77+
Here's a sample of a singleton using an advanced client which handles all three sub-protocols. This code assumes you've got the following external classes:
78+
79+
- **`UserManager`** to check whether the user is logged in, perform associated checks on errors and responses to see if they need to reauthenticate, and perform reauthentication
80+
- **`Logger`** to handle printing logs based on their level, and which supports `.debug`, `.error`, or `.always` log levels.
81+
82+
```swift
83+
// MARK: - Singleton Wrapper
84+
85+
class Apollo {
86+
static let shared = Apollo()
87+
88+
// Configure the network transport to use the singleton as the delegate.
89+
private lazy var networkTransport = HTTPNetworkTransport(
90+
url: URL(string: "http://localhost:8080/graphql")!,
91+
delegate: self
92+
)
93+
94+
// Use the configured network transport in your client.
95+
private(set) lazy var client = ApolloClient(networkTransport: self.networkTransport)
96+
}
97+
98+
// MARK: - Pre-flight delegate
99+
100+
extension Apollo: HTTPNetworkTransportPreflightDelegate {
101+
102+
func networkTransport(_ networkTransport: HTTPNetworkTransport,
103+
shouldSend request: URLRequest) -> Bool {
104+
// If there's an authenticated user, send the request. If not, don't.
105+
return UserManager.shared.hasAuthenticatedUser
106+
}
107+
108+
func networkTransport(_ networkTransport: HTTPNetworkTransport,
109+
willSend request: inout URLRequest) {
110+
111+
// Get the existing headers, or create new ones if they're nil
112+
var headers = request.allHTTPHeaders ?? [String: String]()
113+
114+
// Add any new headers you need
115+
headers["Authentication"] = "Bearer \(UserManager.shared.currentAuthToken)"
116+
117+
// Re-assign the updated headers to the request.
118+
request.headers = headers
119+
120+
Logger.log(.debug, "Outgoing request: \(request)")
121+
}
122+
}
123+
124+
// MARK: - Task Completed Delegate
125+
126+
extension Apollo: HTTPNetworkTransportTaskCompletedDelegate {
127+
func networkTransport(_ networkTransport: HTTPNetworkTransport,
128+
didCompleteRawTaskForRequest request: URLRequest,
129+
withData data: Data?,
130+
response: URLResponse?,
131+
error: Error?) {
132+
Logger.log(.debug, "Raw task completed for request: \(request)")
133+
134+
if let error = error {
135+
Logger.log(.error, "Error: \(error)")
136+
}
137+
138+
if let response = response {
139+
Logger.log(.debug, "Response: \(response)")
140+
} else {
141+
Logger.log(.error, "No URL Response received!")
142+
}
143+
144+
if let data = data {
145+
Logger.log(.debug, "Data: \(String(describing: String(bytes: data, encoding: .utf8)))")
146+
} else {
147+
Logger.log(.error, "No data received!")
148+
}
149+
}
150+
}
151+
152+
// MARK: - Retry Delegate
153+
154+
extension Apollo: HTTPNetworkTransportRetryDelegate {
26155

27-
> Right now, additional headers can only be specified when creating a client. We're working on a better solution for dynamic configuration of the network transport, including the ability to retry requests that failed after refreshing an access token. Please chime in on https://github.com/apollographql/apollo-ios/issues/37 to help shape the design of this feature or to contribute to it.
156+
func networkTransport(_ networkTransport: HTTPNetworkTransport,
157+
receivedError error: Error,
158+
for request: URLRequest,
159+
response: URLResponse?,
160+
retryHandler: @escaping (_ shouldRetry: Bool) -> Void) {
161+
// Check if the error and/or response you've received are something that requires authentication
162+
guard UserManager.shared.requiresReAuthentication(basedOn: error, response: response) else {
163+
// This is not something this application can handle, do not retry.
164+
shouldRetry(false)
165+
}
166+
167+
// Attempt to re-authenticate asynchronously
168+
UserManager.shared.reAuthenticate { success in
169+
// If re-authentication succeeded, try again. If it didn't, don't.
170+
shouldRetry(success)
171+
}
172+
}
173+
}
174+
```

docs/source/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ In this case, the `check-and-run-apollo-cli.sh` file is bundled into the framewo
9797
# Do some magic so we can make sure `FRAMEWORK_SEARCH_PATHS` works correctly when there's a space in the scheme or the folder name.
9898
QUOTED_FRAMEWORK_SEARCH_PATHS=\"$(echo $FRAMEWORK_SEARCH_PATHS | tr -d '"' | sed -e 's/ \//" "\//g')\"
9999

100-
APOLLO_FRAMEWORK_PATH ="$(eval find ${QUOTED_FRAMEWORK_SEARCH_PATHS} -name "Apollo.framework" -maxdepth 1)"
100+
APOLLO_FRAMEWORK_PATH="$(eval find ${QUOTED_FRAMEWORK_SEARCH_PATHS} -name "Apollo.framework" -maxdepth 1)"
101101

102102
if [ -z "${APOLLO_FRAMEWORK_PATH}" ]; then
103103
echo "error: Couldn't find Apollo.framework in FRAMEWORK_SEARCH_PATHS; make sure to add the framework to your project."

0 commit comments

Comments
 (0)