Senior / Staff iOS — interview prep
Networking & data
URLSession · layers · errors · cache · pagination · offline
CodableURLCacheRetryIdempotencyCursorOutbox
01
Layer shape
Stacki
Separate transport (how you call the network), endpoints (paths, method, headers), decoding, and domain errors — features depend on protocols, not raw
URLSession everywhere.| Piece | Owns | Why |
|---|---|---|
| Client / session | URLSession, auth adapters, logging, default timeouts. | One place to tune behavior and swap fakes in tests. |
| Endpoints | Routes, query keys, body encoding — often enums or small structs. | Prevents stringly-typed URLs scattered across features. |
| Mapper | JSON → models; HTTP status → typed app errors. | UI and repositories see meaningful failures, not URLError soup. |
02
Errors, retries & UX
Resilience!
Retry transient failures (timeouts, connectivity) with backoff — not 4xx or broken contracts. Writes need idempotency or server-side dedup (request IDs) before blind replay.
Classify first
- Map to app errors: unauthorized, not found, server, decoding, offline.
- Log correlation IDs for support; avoid logging secrets or full tokens.
UX
- Show cached data when possible; clear empty vs error vs offline.
- Offer manual retry; don’t spin forever on background refresh.
Status + decode
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw APIError.invalidResponse
}
switch http.statusCode {
case 200..<300: break
case 401: throw APIError.unauthorized
default: throw APIError.http(http.statusCode)
}
return try decoder.decode(T.self, from: data)
03
Caching
LayersWhere
- Memory — hot images, session buffers; fast, evicted under pressure.
- Disk —
URLCache, files; survives restart. - Database — queryable offline source of truth with staleness rules.
Policy
- Per-resource TTL, etag / version headers, invalidate on logout.
- Stale-while-revalidate for feeds: show cache, refresh in background.
URLCache + session
var config = URLSessionConfiguration.default
config.urlCache = URLCache(
memoryCapacity: 50 * 1_024 * 1_024,
diskCapacity: 200 * 1_024 * 1_024,
directory: FileManager.default.urls(
for: .cachesDirectory, in: .userDomainMask
).first
)
config.requestCachePolicy = .returnCacheDataElseLoad
let session = URLSession(configuration: config)
04
Pagination & feeds
ScaleCursor / token
Prefer cursor or opaque next token for changing lists — avoids offset skips when rows shift. Client passes the marker back verbatim.
UI
- Prefetch the next page before the user hits the end.
- Dedupe loads; keep loading state per direction (head/tail).
- Keep cell work light; image decode off the hot path.
05
Offline-first & sync
Writes✓
Local reads from DB/cache; writes enqueue to an outbox with stable client IDs — drain when online with retries and ordering per entity.
Conflicts
- Pick strategy with product: last-write-wins, server wins, merge fields, or user resolve.
- Use versions / etag /
updatedAtto detect clashes.
APIs
- Idempotent endpoints + request IDs so retries don’t double-charge.
- Optimistic UI only when rollback is acceptable; else show pending.