Is HTTPCookieStorage.shared.setCookies(_:for:mainDocumentURL:) synchronous or asynchronous?

Hi Team,

I'm trying to understand the behavior of the following API:

HTTPCookieStorage.shared.setCookies(_:for:mainDocumentURL:)

Specifically, does this API persist cookies synchronously, or does it perform the storage asynchronously in the background?

Our use case is storing FCAP (frequency capping) cookies so they persist across app sessions. We call setCookies(_:for:mainDocumentURL:) and want to know whether the cookies are guaranteed to be written before the method returns, or if the actual persistence happens asynchronously.

I couldn't find documentation describing the persistence semantics of this API, so I'd appreciate any clarification or guidance from Apple or anyone familiar with its implementation.

Thanks

Answered by DTS Engineer in 898554022

Thanks for asking. You are right that the reference does not state the persistence semantics. Here is what is documented, and what is only observed.

On synchronous versus asynchronous: nothing classifies the call, not the reference, not the Foundation SDK header (NSHTTPCookieStorage.h), and not the cookie standards. The method returns no value and takes no completion handler, and nothing documents when the change becomes visible or when it reaches disk. The standards do not answer it either, since they define the cookie model, not the timing of an API call. So there is no contract to depend on in either direction.

I can offer an observation, but not a guarantee: on macOS and on iOS 26.5, a cookies or cookies(for:) read on the same storage immediately after setCookies(_:for:mainDocumentURL:) returned the cookies every time. Because that behavior is undocumented, it is subject to change without notice at any time. I would treat it as current behavior on the versions I tested, not something to rely on.

On persistence across app sessions, the behavior follows the standard cookie model, and here there is a rule you can rely on. Under RFC 6265, a cookie is persistent only if it carries a Max-Age or Expires attribute. A cookie with neither is a session cookie, and the client is required to discard it once the session ends. Foundation reflects this: a cookie with no expiration date reports isSessionOnly == true. The documentation describes such a cookie as discarded at the end of the session, regardless of expiration date. So an expiration date is what makes a cookie persist beyond the session, by the standard rather than as an implementation detail. Testing on iOS 26.5 matched this. A cookie with an expires date survived a relaunch, including after the previous instance was force-terminated. A session-only cookie was gone in the next launch.

So if cookies are not surviving across sessions, the first thing to check is whether they carry an expiration date. To store one that persists, give it an expires date when you create it (or a Max-Age / Expires attribute in the response headers you build it from). Then confirm the storage's cookieAcceptPolicy is not .never, and that the domain and path match what you later read.

The dependable part is that lifetime rule: session versus persistent is the standard cookie behavior, and isSessionOnly reflects it. The synchronicity and the exact write timing are not specified anywhere. I would not depend on them beyond what you can observe on the OS versions you test.

For reference:

Thanks for asking. You are right that the reference does not state the persistence semantics. Here is what is documented, and what is only observed.

On synchronous versus asynchronous: nothing classifies the call, not the reference, not the Foundation SDK header (NSHTTPCookieStorage.h), and not the cookie standards. The method returns no value and takes no completion handler, and nothing documents when the change becomes visible or when it reaches disk. The standards do not answer it either, since they define the cookie model, not the timing of an API call. So there is no contract to depend on in either direction.

I can offer an observation, but not a guarantee: on macOS and on iOS 26.5, a cookies or cookies(for:) read on the same storage immediately after setCookies(_:for:mainDocumentURL:) returned the cookies every time. Because that behavior is undocumented, it is subject to change without notice at any time. I would treat it as current behavior on the versions I tested, not something to rely on.

On persistence across app sessions, the behavior follows the standard cookie model, and here there is a rule you can rely on. Under RFC 6265, a cookie is persistent only if it carries a Max-Age or Expires attribute. A cookie with neither is a session cookie, and the client is required to discard it once the session ends. Foundation reflects this: a cookie with no expiration date reports isSessionOnly == true. The documentation describes such a cookie as discarded at the end of the session, regardless of expiration date. So an expiration date is what makes a cookie persist beyond the session, by the standard rather than as an implementation detail. Testing on iOS 26.5 matched this. A cookie with an expires date survived a relaunch, including after the previous instance was force-terminated. A session-only cookie was gone in the next launch.

So if cookies are not surviving across sessions, the first thing to check is whether they carry an expiration date. To store one that persists, give it an expires date when you create it (or a Max-Age / Expires attribute in the response headers you build it from). Then confirm the storage's cookieAcceptPolicy is not .never, and that the domain and path match what you later read.

The dependable part is that lifetime rule: session versus persistent is the standard cookie behavior, and isSessionOnly reflects it. The synchronicity and the exact write timing are not specified anywhere. I would not depend on them beyond what you can observe on the OS versions you test.

For reference:

Hi Team,

Thank you for the response. I'd like to share some of my observation regarding the behavior of the setCookies(_:for:mainDocumentURL:) method that we have encountered.

We noticed that the cookies we are setting include Max-Age and Expires attributes. While we are able to retrieve these cookies immediately after calling setCookies(_:for:mainDocumentURL:), they do not appear to persist on disk immediately.

We believe the system initially stores these cookies in a temporary storage, flushing them to disk only after a delay. We observed this because:

  • If we kill the app immediately after setting the cookie, it is unavailable upon relaunch.

  • If we wait a few seconds (~3–5 seconds) before killing the app, the cookie is successfully retrieved in the next session.

We have seen similar asynchronous behavior with UserDefaults.standard in the past. To work around this for our current needs, we shifted our implementation to store these cookies manually in FileManager using data.write(to:options:).

While this workaround resolves our immediate issue, we would appreciate any guidance on whether this is expected behavior for setCookies. Is there a recommended way to force a flush to disk, or a best practice to ensure session cookies are persisted reliably before the app is terminated?

Thanks

Thanks for following up with the timing detail.

There is no API for forcing a flush. HTTPCookieStorage (https://developer.apple.com/documentation/foundation/httpcookiestorage) declares no flush, no synchronize, and no method that takes a completion handler.

On what to expect, the attribute you are relying on promises less than it appears to. From RFC 6265 (https://www.rfc-editor.org/rfc/rfc6265.html), section 4.1.2.1:

"The Expires attribute indicates the maximum lifetime of the cookie, represented as the date and time at which the cookie expires. The user agent is not required to retain the cookie until the specified date has passed. In fact, user agents often evict cookies due to memory pressure or privacy concerns."

Section 4.1.2.2 says the same of Max-Age, and draft-ietf-httpbis-rfc6265bis (https://www.ietf.org/archive/id/draft-ietf-httpbis-rfc6265bis-20.txt), which is intended to replace RFC 6265, keeps both sentences. So an expiry attribute sets an upper limit on how long a cookie may live. It is not a guarantee that the cookie is still there. Neither document says anything about when a write becomes durable, and neither does the Foundation documentation.

So the interval you measured is not something to build on. A delay you can observe is a property of one build on one device, and observing it does not make it specified. The relaunch I described last time is an observation of the same kind, and carries no more weight.

A value that has to survive an arbitrary termination needs storage whose durability you control. That is what you have already done. HTTPCookie (https://developer.apple.com/documentation/foundation/httpcookie) exposes a properties dictionary, and HTTPCookie(properties:) rebuilds a cookie from one. So a cookie can round-trip through your own file and go back into the store at launch with setCookie(_:).

You are welcome to file an enhancement request through Feedback Assistant (https://feedbackassistant.apple.com) asking for a flush API, or for documentation of when a write becomes durable. If you post the number here, I will check on it the next time I sweep these threads.

Is HTTPCookieStorage.shared.setCookies(_:for:mainDocumentURL:) synchronous or asynchronous?
 
 
Q