
ios-networking
Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.
- Installs
- 4.4K
- Last 24h
- +17
- Trending rank
- #6497
- Security
- Pass
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill ios-networking- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads ios-networking automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
Security audits
Independent scans of the skill's instructions and bundled files.
SKILL.md
The instructions your agent reads when the skill is triggered.
iOS Networking
Use URLSession with async/await and structured concurrency for ordinary HTTP, REST, uploads, downloads, and streaming. Use Network.framework for lower-level protocols and delegate/task APIs for durable background transfers.
Contents
- Core URLSession async/await
- API Client Architecture
- Error Handling
- Pagination
- Network Reachability
- Configuring URLSession
- App Transport Security (ATS)
- Common Mistakes
- Review Checklist
- References
Core URLSession async/await
URLSession gained native async/await overloads in iOS 15. Prefer these for foreground data, upload, download, and streaming work. Background URLSession transfers are the main exception: they still use task/delegate APIs so the system can deliver events after suspension or relaunch.
Validate networking policy locally with URLProtocol fixtures for valid 2xx,
malformed 2xx, one-time 401 refresh, bounded 429/5xx retry, timeout/offline,
cancellation, and nonretryable 4xx. Inspect headers, status, and error
classification; fix the policy and rerun. Retry only safe/idempotent or
explicitly replayable requests, and never loop token refresh.
Data Requests
// Basic GET
let (data, response) = try await URLSession.shared.data(from: url)
// With a configured URLRequest
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(payload)
request.timeoutInterval = 30
request.cachePolicy = .reloadIgnoringLocalCacheData
let (data, response) = try await URLSession.shared.data(for: request)
Response Validation
Always validate the HTTP status code before decoding. URLSession does not throw for 4xx/5xx responses -- it only throws for transport-level failures.
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
guard (200..<300).contains(httpResponse.statusCode) else {
throw NetworkError.httpError(
statusCode: httpResponse.statusCode,
data: data
)
}
JSON Decoding with Codable
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200..<300).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.keyDecodingStrategy = .convertFromSnakeCase
return try decoder.decode(T.self, from: data)
}
Downloads and Uploads
Use download(for:) for large files -- it streams to disk instead of
loading the entire payload into memory.
// Download to a temporary file
let (localURL, response) = try await URLSession.shared.download(for: request)
// Move or copy the returned temporary file promptly.
let destination = documentsDirectory.appendingPathComponent("file.zip")
try FileManager.default.moveItem(at: localURL, to: destination)
For delegate-based URLSessionDownloadDelegate, move or open the temporary
file before urlSession(_:downloadTask:didFinishDownloadingTo:) returns.
Background sessions are delegate-driven transfer queues. Use task creation
APIs such as downloadTask(with:) and file-backed uploadTask(with:fromFile:),
then handle URLSessionDelegate / task delegate callbacks. Do not use async
convenience APIs such as data(for:), download(for:), or upload(for:) as
the durable background-session pattern.
// Upload data
let (data, response) = try await URLSession.shared.upload(for: request, from: bodyData)
// Upload from file
let (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)
Streaming with AsyncBytes
Use bytes(for:) for streaming responses, progress tracking, or
line-delimited data (e.g., server-sent events).
let (bytes, response) = try await URLSession.shared.bytes(for: request)
for try await line in bytes.lines {
// Process each line as it arrives (e.g., SSE stream)
handleEvent(line)
}
API Client Architecture
Protocol-Based Client
Define a protocol for testability. This lets you swap implementations in tests without mocking URLSession directly.
protocol APIClientProtocol: Sendable {
func fetch<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint
) async throws -> T
func send<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint,
body: some Encodable & Sendable
) async throws -> T
}
struct Endpoint: Sendable {
let path: String
var method: String = "GET"
var queryItems: [URLQueryItem] = []
var headers: [String: String] = [:]
func url(relativeTo baseURL: URL) -> URL {
guard let components = URLComponents(
url: baseURL.appendingPathComponent(path),
resolvingAgainstBaseURL: true
) else {
preconditionFailure("Invalid URL components for path: \(path)")
}
var mutableComponents = components
if !queryItems.isEmpty {
mutableComponents.queryItems = queryItems
}
guard let url = mutableComponents.url else {
preconditionFailure("Failed to construct URL from components")
}
return url
}
}
The client accepts a baseURL, optional custom URLSession, JSONDecoder,
and an array of RequestMiddleware interceptors. Each method builds a
URLRequest from the endpoint, applies middleware, executes the request,
validates the status code, and decodes the result. See
references/urlsession-patterns.md for the complete APIClient implementation
with convenience methods, request builder, and test setup.
Production clients should receive an injected, configured URLSession instead
of calling URLSession.shared internally. Configure URLSessionConfiguration
with request/resource timeouts, cache policy or URLCache,
waitsForConnectivity, data-cost policy, and delegates when authentication
challenges, redirects, metrics, pinning, or background transfer handling matter.
Lightweight Closure-Based Client
For apps using the MV pattern, use closure-based clients for testability and SwiftUI preview support. See references/lightweight-clients.md for the full pattern (struct of async closures, injected via init).
Request Middleware / Interceptors
Middleware transforms requests before they are sent. Use this for authentication, logging, analytics headers, and similar cross-cutting concerns.
protocol RequestMiddleware: Sendable {
func prepare(_ request: URLRequest) async throws -> URLRequest
}
struct AuthMiddleware: RequestMiddleware {
let tokenProvider: @Sendable () async throws -> String
func prepare(_ request: URLRequest) async throws -> URLRequest {
var request = request
let token = try await tokenProvider()
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
return request
}
}
Token Refresh Flow
Handle 401 responses by refreshing the token and retrying once.
func fetchWithTokenRefresh<T: Decodable & Sendable>(
_ type: T.Type,
endpoint: Endpoint,
tokenStore: TokenStore
) async throws -> T {
do {
return try await fetch(type, endpoint: endpoint)
} catch NetworkError.httpError(statusCode: 401, _) {
try await tokenStore.refreshToken()
return try await fetch(type, endpoint: endpoint)
}
}
Error Handling
Structured Error Types
enum NetworkError: Error, Sendable {
case invalidResponse
case httpError(statusCode: Int, data: Data)
case decodingFailed(Error)
case noConnection
case timedOut
case cancelled
/// Map a URLError to a typed NetworkError
static func from(_ urlError: URLError) -> NetworkError {
switch urlError.code {
case .notConnectedToInternet, .networkConnectionLost:
return .noConnection
case .timedOut:
return .timedOut
case .cancelled:
return .cancelled
default:
return .httpError(statusCode: -1, data: Data())
}
}
}
Key URLError Cases
| URLError Code | Meaning | Action |
|---|---|---|
.notConnectedToInternet | Device offline | Show offline UI, queue for retry |
.networkConnectionLost | Connection dropped mid-request | Retry with backoff |
.timedOut | Server did not respond in time | Retry once, then show error |
.cancelled | Task was cancelled | No action needed; do not show error |
.cannotFindHost | DNS failure | Check URL, show error |
.secureConnectionFailed | TLS handshake failed | Check cert pinning, ATS config |
.userAuthenticationRequired | Authentication required to access a resource | Trigger auth flow |
Decoding Server Error Bodies
struct APIErrorResponse: Decodable, Sendable {
let code: String
let message: String
}
func decodeAPIError(from data: Data) -> APIErrorResponse? {
try? JSONDecoder().decode(APIErrorResponse.self, from: data)
}
// Usage in catch block
catch NetworkError.httpError(let statusCode, let data) {
if let apiError = decodeAPIError(from: data) {
showError("Server error: \(apiError.message)")
} else {
showError("HTTP \(statusCode)")
}
}
Retry with Exponential Backoff
Use structured concurrency for retries. Respect task cancellation between attempts. Skip retries for cancellation and 4xx client errors (except 429).
func withRetry<T: Sendable>(
maxAttempts: Int = 3,
initialDelay: Duration = .seconds(1),
operation: @Sendable () async throws -> T
) async throws -> T {
var lastError: Error?
for attempt in 0..<maxAttempts {
do {
return try await operation()
} catch {
lastError = error
if error is CancellationError { throw error }
if case NetworkError.httpError(let code, _) = error,
(400..<500).contains(code), code != 429 { throw error }
if attempt < maxAttempts - 1 {
try await Task.sleep(for: initialDelay * Int(pow(2.0, Double(attempt))))
}
}
}
throw lastError!
}
Pagination
Build cursor-based or offset-based pagination with AsyncSequence.
Always check Task.isCancelled between pages. See
references/urlsession-patterns.md for complete CursorPaginator and
offset-based implementations.
Network Reachability
Use NWPathMonitor from the Network framework -- not third-party
Reachability libraries. On current OS targets it conforms to AsyncSequence;
wrap pathUpdateHandler only for compatibility or custom projections.
import Network
func observeNetworkStatus() async {
let monitor = NWPathMonitor()
for await path in monitor {
handle(path.status)
}
}
Check path.isExpensive (cellular) and path.isConstrained (Low Data
Mode) to adapt behavior (reduce image quality, skip prefetching).
Use Network.framework for low-level TCP, UDP, listeners, Bonjour, path
monitoring, or WebSocket protocol work -- not ordinary REST APIs. For iOS 26
NetworkConnection<QUIC>, openStream(...) and inboundStreams(...) are
async throwing APIs; see references/network-framework.md#quic-multiplexed-streams.
Configuring URLSession
Inject a configured session when production code needs timeouts, caching,
connectivity waiting, data-cost policy, authentication challenges, redirects,
metrics, or background delegates. Use URLSession.shared only for simple
one-off work. See URLSession patterns for
the full configuration and test setup.
App Transport Security (ATS)
ATS makes HTTPS the URL Loading System default. Do not enable blanket arbitrary
loads; use the narrowest justified domain/local-network exception. Configure TLS
explicitly for Network.framework. Keep deep trust and SPKI pinning design in
swift-security.
Common Mistakes
DON'T: Force-unwrap URL(string:) with dynamic input.
DO: Use URL(string:) with proper error handling. Force-unwrap is
acceptable only for compile-time-constant strings.
DON'T: Decode JSON on the main thread for large payloads.
DO: Keep decoding on the calling context of the URLSession call, which
is off-main by default. Only hop to @MainActor to update UI state.
DON'T: Ignore cancellation in long-running network tasks.
DO: Check Task.isCancelled or call try Task.checkCancellation() in
loops (pagination, streaming, retry). Use .task in SwiftUI for automatic
cancellation.
DON'T: Use Alamofire or Moya when URLSession async/await handles the need. DO: Use URLSession directly. With async/await, the ergonomic gap that justified third-party libraries no longer exists. Reserve third-party libraries for genuinely missing features (e.g., image caching).
DON'T: Mock URLSession directly in tests.
DO: Use URLProtocol subclass for transport-level mocking, or use
protocol-based clients that accept a test double.
DON'T: Fire network requests from body or view initializers.
DO: Use .task or .task(id:) to trigger network calls.
Review Checklist
- Foreground transfers use async/await; background sessions use delegate/task APIs
- Error handling covers URLError cases (.notConnectedToInternet, .timedOut, .cancelled)
- Requests are cancellable (respect Task cancellation via
.taskmodifier or stored Task references) - Authentication tokens injected via middleware, not hardcoded
- Response HTTP status codes validated before decoding
- Large downloads use
download(for:)notdata(for:) - Network calls happen off
@MainActor(only UI updates on main) - URLSession configured with appropriate timeouts and caching
- Production clients inject configured sessions instead of using
URLSession.shared - Background transfers use task/delegate APIs, not async convenience APIs
- Retry logic excludes cancellation and 4xx client errors
- Pagination checks
Task.isCancelledbetween pages - Sensitive tokens stored in Keychain (not UserDefaults or plain files)
- No force-unwrapped URLs from dynamic input
- Server error responses decoded and surfaced to users
- Network.framework code configures TLS/trust explicitly and keeps deep pinning work in
swift-security -
NetworkConnection<QUIC>stream APIs are treated as async throwing - Ensure network response model types conform to Sendable; use @MainActor for UI-updating completion paths
References
- See references/urlsession-patterns.md for complete API client implementation, multipart uploads, download progress, URLProtocol mocking, retry/backoff, certificate pinning, request logging, and pagination implementations.
- See references/background-websocket.md for background URLSession configuration, background downloads/uploads, WebSocket patterns with structured concurrency, and reconnection strategies.
- See references/lightweight-clients.md for the lightweight closure-based client pattern (struct of async closures, injected via init for testability and preview support).
- See references/network-framework.md for Network.framework (NWConnection, NWListener, NWBrowser, NWPathMonitor) and low-level TCP/UDP/WebSocket patterns.
- See references/file-storage-patterns.md for file system directory selection, FileProtectionType, backup exclusion, and storage pressure handling.
FAQ
What does the ios-networking skill do?
Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.
How do I install ios-networking?
Run npx skills add https://github.com/dpearson2699/swift-ios-skills --skill ios-networking in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/ios-networking/ (personal) or .claude/skills/ios-networking/ (project).
Which AI agents can use ios-networking?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is ios-networking safe to install?
Security partners have reviewed it: Gen Agent Trust Hub passed; Socket passed; Snyk passed; ZeroLeaks passed. As with any skill, read the SKILL.md and bundled scripts before installing.
Who maintains ios-networking?
It is published by dpearson2699 in dpearson2699/swift-ios-skills. It has 4,408 recorded installs.
More from dpearson2699/swift-ios-skills
swiftui-animation
dpearson2699/swift-ios-skills
Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.
ios-accessibility
dpearson2699/swift-ios-skills
Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.
swiftui-performance
dpearson2699/swift-ios-skills
Profile, diagnose, and remediate SwiftUI runtime performance using code review, Instruments, and repeatable measurements. Use when a SwiftUI screen renders slowly, scrolling or animations hitch, view bodies update excessively, list identity churns, layout work spikes, or broad Observation dependencies raise CPU cost. Covers evidence-based triage, SwiftUI Instruments lanes, lazy-container guardrails, state lifetime, and before/after verification.
swiftui-patterns
dpearson2699/swift-ios-skills
Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.
app-store-review
dpearson2699/swift-ios-skills
Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.
swiftui-navigation
dpearson2699/swift-ios-skills
Implement SwiftUI navigation patterns including NavigationStack, NavigationSplitView, sheet presentation, tab-based navigation, and deep linking. Use when building push navigation, programmatic routing, multi-column layouts, modal sheets, tab bars, universal links, or custom URL scheme handling.
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.