Practical use of the inout parameter in Swift
inout who?
The inout keyword enables pass-by-reference semantics for value types. Mark a parameter inout and the function receives a reference to the original — not a copy. The & at the call site makes mutation explicit:
var request = URLRequest(url: url)
modifyHTTPHeadersIfNeeded(&request) // `&` signals that request may be modified
Custom HTTP headers in a WKWebView request
When you need to inject custom HTTP headers into WKWebView navigation requests, you run into a constraint: the WKNavigationDelegate method webView(_:decidePolicyFor:) lets you intercept navigations but not modify the underlying request directly. The inout pattern gives you a clean way to build and apply those mutations before loading.
1. Load
public func load(_ request: URLRequest) {
var newRequest = request
modifyHTTPHeadersIfNeeded(&newRequest)
webView.load(newRequest)
}
URLRequest arrives as an immutable value. Assigning it to a var creates a mutable copy; & lets modifyHTTPHeadersIfNeeded write back through it.
2. Modify
private func modifyHTTPHeadersIfNeeded(_ request: inout URLRequest) {
if isCustomHTTPHeadersAdded(&request) {
return
}
var headers = request.allHTTPHeaderFields ?? [:]
if let providedHeaders = delegate?.provideHeaders() {
for (key, value) in providedHeaders {
headers[key] = value
}
}
addRequiredHTTPHeaders(&headers)
request.allHTTPHeaderFields = headers
}
The early return on isCustomHTTPHeadersAdded is the guard that prevents infinite navigation loops — covered in detail below.
3. Intercept
public func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction
) async -> WKNavigationActionPolicy {
guard
let url = navigationAction.request.url,
isDomainTrusted(url)
else {
return .cancel
}
var request = navigationAction.request
if isCustomHTTPHeadersAdded(&request) {
return .allow
} else {
var newRequest = navigationAction.request
modifyHTTPHeadersIfNeeded(&newRequest)
webView.load(newRequest)
return .cancel
}
}
When a navigation fires, check whether custom headers have already been added. If not, cancel the navigation, inject the headers, and reload. The reloaded request passes the check, so the second pass through the delegate returns .allow.
Avoiding infinite loops
isCustomHTTPHeadersAdded is what makes the cancel-and-reload pattern safe:
private func isCustomHTTPHeadersAdded(_ request: inout URLRequest) -> Bool {
guard let headers = request.allHTTPHeaderFields else {
return false
}
let customHeaderKey = RequiredHeaders.customAdded
return headers[customHeaderKey.headerName] == customHeaderKey.headerValue
}
Without this guard, every navigation would be cancelled, modified, and reloaded — indefinitely.
Benefits of inout
Performance. Value types in Swift are copied on assignment. inout avoids an extra copy when passing large structs — relevant for URLRequest objects carrying headers, body data, and metadata.
Explicit mutations. The & at every call site is a contract: this function may change what you pass in. Readers don’t need to inspect the signature to know mutation is happening.
Composability. Each function owns one step; inout threads the request through the chain.