{"id":"ea797c62-a2db-413c-8fc3-b4926f6c289f","shortId":"7dpzyx","kind":"skill","title":"Ios Networking","tagline":"Swift Ios Skills skill by Dpearson2699","description":"# iOS Networking\n\nModern networking patterns for iOS 26+ using URLSession with async/await and\nstructured concurrency. All examples target Swift 6.3. No third-party\ndependencies required -- URLSession covers the vast majority of networking\nneeds.\n\n## Contents\n\n- [Core URLSession async/await](#core-urlsession-asyncawait)\n- [API Client Architecture](#api-client-architecture)\n- [Error Handling](#error-handling)\n- [Pagination](#pagination)\n- [Network Reachability](#network-reachability)\n- [Configuring URLSession](#configuring-urlsession)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n## Core URLSession async/await\n\nURLSession gained native async/await overloads in iOS 15. These are the\nonly networking APIs to use in new code. Never use completion-handler\nvariants in new projects.\n\n### Data Requests\n\n```swift\n// Basic GET\nlet (data, response) = try await URLSession.shared.data(from: url)\n\n// With a configured URLRequest\nvar request = URLRequest(url: url)\nrequest.httpMethod = \"POST\"\nrequest.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\nrequest.httpBody = try JSONEncoder().encode(payload)\nrequest.timeoutInterval = 30\nrequest.cachePolicy = .reloadIgnoringLocalCacheData\n\nlet (data, response) = try await URLSession.shared.data(for: request)\n```\n\n### Response Validation\n\nAlways validate the HTTP status code before decoding. URLSession does not\nthrow for 4xx/5xx responses -- it only throws for transport-level failures.\n\n```swift\nguard let httpResponse = response as? HTTPURLResponse else {\n    throw NetworkError.invalidResponse\n}\n\nguard (200..<300).contains(httpResponse.statusCode) else {\n    throw NetworkError.httpError(\n        statusCode: httpResponse.statusCode,\n        data: data\n    )\n}\n```\n\n### JSON Decoding with Codable\n\n```swift\nfunc fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {\n    let (data, response) = try await URLSession.shared.data(from: url)\n\n    guard let httpResponse = response as? HTTPURLResponse,\n          (200..<300).contains(httpResponse.statusCode) else {\n        throw NetworkError.invalidResponse\n    }\n\n    let decoder = JSONDecoder()\n    decoder.dateDecodingStrategy = .iso8601\n    decoder.keyDecodingStrategy = .convertFromSnakeCase\n    return try decoder.decode(T.self, from: data)\n}\n```\n\n### Downloads and Uploads\n\nUse `download(for:)` for large files -- it streams to disk instead of\nloading the entire payload into memory.\n\n```swift\n// Download to a temporary file\nlet (localURL, response) = try await URLSession.shared.download(for: request)\n\n// Move from temp location before the method returns\nlet destination = documentsDirectory.appendingPathComponent(\"file.zip\")\ntry FileManager.default.moveItem(at: localURL, to: destination)\n```\n\n```swift\n// Upload data\nlet (data, response) = try await URLSession.shared.upload(for: request, from: bodyData)\n\n// Upload from file\nlet (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)\n```\n\n### Streaming with AsyncBytes\n\nUse `bytes(for:)` for streaming responses, progress tracking, or\nline-delimited data (e.g., server-sent events).\n\n```swift\nlet (bytes, response) = try await URLSession.shared.bytes(for: request)\n\nfor try await line in bytes.lines {\n    // Process each line as it arrives (e.g., SSE stream)\n    handleEvent(line)\n}\n```\n\n## API Client Architecture\n\n### Protocol-Based Client\n\nDefine a protocol for testability. This lets you swap implementations in\ntests without mocking URLSession directly.\n\n```swift\nprotocol APIClientProtocol: Sendable {\n    func fetch<T: Decodable & Sendable>(\n        _ type: T.Type,\n        endpoint: Endpoint\n    ) async throws -> T\n\n    func send<T: Decodable & Sendable>(\n        _ type: T.Type,\n        endpoint: Endpoint,\n        body: some Encodable & Sendable\n    ) async throws -> T\n}\n```\n\n```swift\nstruct Endpoint: Sendable {\n    let path: String\n    var method: String = \"GET\"\n    var queryItems: [URLQueryItem] = []\n    var headers: [String: String] = [:]\n\n    func url(relativeTo baseURL: URL) -> URL {\n        guard let components = URLComponents(\n            url: baseURL.appendingPathComponent(path),\n            resolvingAgainstBaseURL: true\n        ) else {\n            preconditionFailure(\"Invalid URL components for path: \\(path)\")\n        }\n        var mutableComponents = components\n        if !queryItems.isEmpty {\n            mutableComponents.queryItems = queryItems\n        }\n        guard let url = mutableComponents.url else {\n            preconditionFailure(\"Failed to construct URL from components\")\n        }\n        return url\n    }\n}\n```\n\nThe client accepts a `baseURL`, optional custom `URLSession`, `JSONDecoder`,\nand an array of `RequestMiddleware` interceptors. Each method builds a\n`URLRequest` from the endpoint, applies middleware, executes the request,\nvalidates the status code, and decodes the result. See\n[references/urlsession-patterns.md](references/urlsession-patterns.md) for the complete `APIClient` implementation\nwith convenience methods, request builder, and test setup.\n\n### Lightweight Closure-Based Client\n\nFor apps using the MV pattern, use closure-based clients for testability\nand SwiftUI preview support. See [references/lightweight-clients.md](references/lightweight-clients.md) for\nthe full pattern (struct of async closures, injected via init).\n\n### Request Middleware / Interceptors\n\nMiddleware transforms requests before they are sent. Use this for\nauthentication, logging, analytics headers, and similar cross-cutting\nconcerns.\n\n```swift\nprotocol RequestMiddleware: Sendable {\n    func prepare(_ request: URLRequest) async throws -> URLRequest\n}\n```\n\n```swift\nstruct AuthMiddleware: RequestMiddleware {\n    let tokenProvider: @Sendable () async throws -> String\n\n    func prepare(_ request: URLRequest) async throws -> URLRequest {\n        var request = request\n        let token = try await tokenProvider()\n        request.setValue(\"Bearer \\(token)\", forHTTPHeaderField: \"Authorization\")\n        return request\n    }\n}\n```\n\n### Token Refresh Flow\n\nHandle 401 responses by refreshing the token and retrying once.\n\n```swift\nfunc fetchWithTokenRefresh<T: Decodable & Sendable>(\n    _ type: T.Type,\n    endpoint: Endpoint,\n    tokenStore: TokenStore\n) async throws -> T {\n    do {\n        return try await fetch(type, endpoint: endpoint)\n    } catch NetworkError.httpError(statusCode: 401, _) {\n        try await tokenStore.refreshToken()\n        return try await fetch(type, endpoint: endpoint)\n    }\n}\n```\n\n## Error Handling\n\n### Structured Error Types\n\n```swift\nenum NetworkError: Error, Sendable {\n    case invalidResponse\n    case httpError(statusCode: Int, data: Data)\n    case decodingFailed(Error)\n    case noConnection\n    case timedOut\n    case cancelled\n\n    /// Map a URLError to a typed NetworkError\n    static func from(_ urlError: URLError) -> NetworkError {\n        switch urlError.code {\n        case .notConnectedToInternet, .networkConnectionLost:\n            return .noConnection\n        case .timedOut:\n            return .timedOut\n        case .cancelled:\n            return .cancelled\n        default:\n            return .httpError(statusCode: -1, data: Data())\n        }\n    }\n}\n```\n\n### Key URLError Cases\n\n| URLError Code | Meaning | Action |\n|---|---|---|\n| `.notConnectedToInternet` | Device offline | Show offline UI, queue for retry |\n| `.networkConnectionLost` | Connection dropped mid-request | Retry with backoff |\n| `.timedOut` | Server did not respond in time | Retry once, then show error |\n| `.cancelled` | Task was cancelled | No action needed; do not show error |\n| `.cannotFindHost` | DNS failure | Check URL, show error |\n| `.secureConnectionFailed` | TLS handshake failed | Check cert pinning, ATS config |\n| `.userAuthenticationRequired` | 401 from proxy | Trigger auth flow |\n\n### Decoding Server Error Bodies\n\n```swift\nstruct APIErrorResponse: Decodable, Sendable {\n    let code: String\n    let message: String\n}\n\nfunc decodeAPIError(from data: Data) -> APIErrorResponse? {\n    try? JSONDecoder().decode(APIErrorResponse.self, from: data)\n}\n\n// Usage in catch block\ncatch NetworkError.httpError(let statusCode, let data) {\n    if let apiError = decodeAPIError(from: data) {\n        showError(\"Server error: \\(apiError.message)\")\n    } else {\n        showError(\"HTTP \\(statusCode)\")\n    }\n}\n```\n\n### Retry with Exponential Backoff\n\nUse structured concurrency for retries. Respect task cancellation between\nattempts. Skip retries for cancellation and 4xx client errors (except 429).\n\n```swift\nfunc withRetry<T: Sendable>(\n    maxAttempts: Int = 3,\n    initialDelay: Duration = .seconds(1),\n    operation: @Sendable () async throws -> T\n) async throws -> T {\n    var lastError: Error?\n    for attempt in 0..<maxAttempts {\n        do {\n            return try await operation()\n        } catch {\n            lastError = error\n            if error is CancellationError { throw error }\n            if case NetworkError.httpError(let code, _) = error,\n               (400..<500).contains(code), code != 429 { throw error }\n            if attempt < maxAttempts - 1 {\n                try await Task.sleep(for: initialDelay * Int(pow(2.0, Double(attempt))))\n            }\n        }\n    }\n    throw lastError!\n}\n```\n\n## Pagination\n\nBuild cursor-based or offset-based pagination with `AsyncSequence`.\nAlways check `Task.isCancelled` between pages. See\n[references/urlsession-patterns.md](references/urlsession-patterns.md) for complete `CursorPaginator` and\noffset-based implementations.\n\n## Network Reachability\n\nUse `NWPathMonitor` from the Network framework — not third-party\nReachability libraries. Wrap in `AsyncStream` for structured concurrency.\n\n```swift\nimport Network\n\nfunc networkStatusStream() -> AsyncStream<NWPath.Status> {\n    AsyncStream { continuation in\n        let monitor = NWPathMonitor()\n        monitor.pathUpdateHandler = { continuation.yield($0.status) }\n        continuation.onTermination = { _ in monitor.cancel() }\n        monitor.start(queue: DispatchQueue(label: \"NetworkMonitor\"))\n    }\n}\n```\n\nCheck `path.isExpensive` (cellular) and `path.isConstrained` (Low Data\nMode) to adapt behavior (reduce image quality, skip prefetching).\n\n## Configuring URLSession\n\nCreate a configured session for production code. `URLSession.shared` is\nacceptable only for simple, one-off requests.\n\n```swift\nlet configuration = URLSessionConfiguration.default\nconfiguration.timeoutIntervalForRequest = 30\nconfiguration.timeoutIntervalForResource = 300\nconfiguration.waitsForConnectivity = true\nconfiguration.requestCachePolicy = .returnCacheDataElseLoad\nconfiguration.httpAdditionalHeaders = [\n    \"Accept\": \"application/json\",\n    \"Accept-Language\": Locale.preferredLanguages.first ?? \"en\"\n]\n\nlet session = URLSession(configuration: configuration)\n```\n\n`waitsForConnectivity = true` is valuable -- it makes the session wait for\na network path instead of failing immediately when offline. Combine with\n`urlSession(_:taskIsWaitingForConnectivity:)` delegate callback for UI\nfeedback.\n\n## Common Mistakes\n\n**DON'T:** Use `URLSession.shared` with custom configuration needs.\n**DO:** Create a configured `URLSession` with appropriate timeouts, caching,\nand delegate for production code.\n\n**DON'T:** Force-unwrap `URL(string:)` with dynamic input.\n**DO:** Use `URL(string:)` with proper error handling. Force-unwrap is\nacceptable only for compile-time-constant strings.\n\n**DON'T:** Decode JSON on the main thread for large payloads.\n**DO:** Keep decoding on the calling context of the URLSession call, which\nis off-main by default. Only hop to `@MainActor` to update UI state.\n\n**DON'T:** Ignore cancellation in long-running network tasks.\n**DO:** Check `Task.isCancelled` or call `try Task.checkCancellation()` in\nloops (pagination, streaming, retry). Use `.task` in SwiftUI for automatic\ncancellation.\n\n**DON'T:** Use Alamofire or Moya when URLSession async/await handles the\nneed.\n**DO:** Use URLSession directly. With async/await, the ergonomic gap that\njustified third-party libraries no longer exists. Reserve third-party\nlibraries for genuinely missing features (e.g., image caching).\n\n**DON'T:** Mock URLSession directly in tests.\n**DO:** Use `URLProtocol` subclass for transport-level mocking, or use\nprotocol-based clients that accept a test double.\n\n**DON'T:** Use `data(for:)` for large file downloads.\n**DO:** Use `download(for:)` which streams to disk and avoids memory spikes.\n\n**DON'T:** Fire network requests from `body` or view initializers.\n**DO:** Use `.task` or `.task(id:)` to trigger network calls.\n\n**DON'T:** Hardcode authentication tokens in requests.\n**DO:** Inject tokens via middleware so they are centralized and refreshable.\n\n**DON'T:** Ignore HTTP status codes and decode blindly.\n**DO:** Validate status codes before decoding. A 200 with invalid JSON and\na 500 with an error body require different handling.\n\n## Review Checklist\n\n- [ ] All network calls use async/await (not completion handlers)\n- [ ] Error handling covers URLError cases (.notConnectedToInternet, .timedOut, .cancelled)\n- [ ] Requests are cancellable (respect Task cancellation via `.task` modifier or stored Task references)\n- [ ] Authentication tokens injected via middleware, not hardcoded\n- [ ] Response HTTP status codes validated before decoding\n- [ ] Large downloads use `download(for:)` not `data(for:)`\n- [ ] Network calls happen off `@MainActor` (only UI updates on main)\n- [ ] URLSession configured with appropriate timeouts and caching\n- [ ] Retry logic excludes cancellation and 4xx client errors\n- [ ] Pagination checks `Task.isCancelled` between pages\n- [ ] Sensitive tokens stored in Keychain (not UserDefaults or plain files)\n- [ ] No force-unwrapped URLs from dynamic input\n- [ ] Server error responses decoded and surfaced to users\n- [ ] Ensure network response model types conform to Sendable; use @MainActor for UI-updating completion paths\n\n## References\n\n- See [references/urlsession-patterns.md](references/urlsession-patterns.md) for complete API client\n  implementation, multipart uploads, download progress, URLProtocol\n  mocking, retry/backoff, certificate pinning, request logging, and\n  pagination implementations.\n- See [references/background-websocket.md](references/background-websocket.md) for background URLSession\n  configuration, background downloads/uploads, WebSocket patterns with\n  structured concurrency, and reconnection strategies.\n- See [references/lightweight-clients.md](references/lightweight-clients.md) for the lightweight closure-based\n  client pattern (struct of async closures, injected via init for testability\n  and preview support).\n- See [references/network-framework.md](references/network-framework.md) for Network.framework (NWConnection,\n  NWListener, NWBrowser, NWPathMonitor) and low-level TCP/UDP/WebSocket patterns.","tags":["ios","networking","swift","skills","dpearson2699"],"capabilities":["skill","source-dpearson2699","category-swift-ios-skills"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/ios-networking","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"install_from":"skills.sh"}},"qualityScore":"0.300","qualityRationale":"deterministic score 0.30 from registry signals: · indexed on skills.sh · published under dpearson2699/swift-ios-skills","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T05:40:37.249Z","embedding":null,"createdAt":"2026-04-18T20:33:22.026Z","updatedAt":"2026-04-22T05:40:37.249Z","lastSeenAt":"2026-04-22T05:40:37.249Z","tsv":"'-1':758 '0':931 '0.status':1039 '1':916,964 '15':97 '2.0':972 '200':201,241,1400 '26':16 '3':912 '30':154,1088 '300':202,242,1090 '400':953 '401':656,688,826 '429':906,958 '4xx':902,1489 '4xx/5xx':180 '500':954,1406 '6.3':28 'accept':500,1075,1096,1099,1182,1321 'accept-languag':1098 'action':767,803 'adapt':1057 'alamofir':1259 'alway':167,989 'analyt':601 'api':51,55,103,387,1545 'api-client-architectur':54 'apicli':540 'apiclientprotocol':412 'apierror':871 'apierror.message':878 'apierrorrespons':838,852 'apierrorresponse.self':856 'app':556 'appli':521 'application/json':143,1097 'appropri':1152,1480 'architectur':53,57,389 'array':509 'arriv':381 'async':224,420,433,581,617,627,634,674,919,922,1592 'async/await':20,46,89,93,1264,1273,1420 'asyncawait':50 'asyncbyt':342 'asyncsequ':988 'asyncstream':1021,1030,1031 'at':823 'attempt':896,929,962,974 'auth':830 'authent':599,1369,1445 'authmiddlewar':622 'author':649 'automat':1254 'avoid':1343 'await':127,161,231,292,321,334,366,372,643,680,690,694,936,966 'background':1566,1569 'backoff':785,886 'base':392,553,564,981,985,1003,1318,1587 'baseurl':457,502 'baseurl.appendingpathcomponent':465 'basic':121 'bearer':646 'behavior':1058 'blind':1392 'block':862 'bodi':429,835,1352,1410 'bodydata':326 'build':515,978 'builder':546 'byte':344,363 'bytes.lines':375 'cach':1154,1297,1483 'call':1206,1211,1241,1365,1418,1468 'callback':1132 'cancel':725,751,753,798,801,894,900,1230,1255,1431,1434,1437,1487 'cancellationerror':944 'cannotfindhost':809 'case':709,711,717,720,722,724,741,746,750,763,948,1428 'catch':685,861,863,938 'category-swift-ios-skills' 'cellular':1050 'central':1381 'cert':821 'certif':1555 'check':812,820,990,1048,1238,1493 'checklist':81,84,1415 'client':52,56,388,393,499,554,565,903,1319,1490,1546,1588 'closur':552,563,582,1586,1593 'closure-bas':551,562,1585 'codabl':215 'code':108,172,529,765,842,951,956,957,1072,1159,1389,1396,1455 'combin':1127 'common':75,78,1136 'common-mistak':77 'compil':1186 'compile-time-const':1185 'complet':112,539,998,1422,1537,1544 'completion-handl':111 'compon':462,473,479,495 'concern':608 'concurr':23,889,1024,1575 'config':824 'configur':70,73,133,1064,1068,1085,1106,1107,1144,1149,1478,1568 'configuration.httpadditionalheaders':1095 'configuration.requestcachepolicy':1093 'configuration.timeoutintervalforrequest':1087 'configuration.timeoutintervalforresource':1089 'configuration.waitsforconnectivity':1091 'configuring-urlsess':72 'conform':1528 'connect':778 'constant':1188 'construct':492 'contain':203,243,955 'content':43,146 'content-typ':145 'context':1207 'continu':1032 'continuation.ontermination':1040 'continuation.yield':1038 'conveni':543 'convertfromsnakecas':254 'core':44,48,87 'core-urlsession-asyncawait':47 'cover':36,1426 'creat':1066,1147 'cross':606 'cross-cut':605 'cursor':980 'cursor-bas':979 'cursorpagin':999 'custom':504,1143 'cut':607 'data':118,124,158,210,211,228,260,316,318,331,355,715,716,759,760,850,851,858,868,874,1054,1328,1465 'decod':174,213,249,531,832,839,855,1192,1203,1391,1398,1458,1518 'decodeapierror':848,872 'decoder.datedecodingstrategy':251 'decoder.decode':257 'decoder.keydecodingstrategy':253 'decodingfail':718 'default':754,1218 'defin':394 'deleg':1131,1156 'delimit':354 'depend':33 'destin':305,313 'devic':769 'differ':1412 'direct':409,1271,1302 'disk':273,1341 'dispatchqueu':1045 'dns':810 'documentsdirectory.appendingpathcomponent':306 'doubl':973,1324 'download':261,265,283,1333,1336,1460,1462,1550 'downloads/uploads':1570 'dpearson2699':8 'drop':779 'durat':914 'dynam':1168,1513 'e.g':356,382,1295 'els':197,205,245,469,488,879 'en':1102 'encod':151,431 'endpoint':418,419,427,428,438,520,670,671,683,684,697,698 'ensur':1523 'entir':278 'enum':705 'ergonom':1275 'error':58,61,699,702,707,719,797,808,815,834,877,904,927,940,942,946,952,960,1176,1409,1424,1491,1516 'error-handl':60 'event':360 'exampl':25 'except':905 'exclud':1486 'execut':523 'exist':1285 'exponenti':885 'fail':490,819,1123 'failur':189,811 'featur':1294 'feedback':1135 'fetch':218,415,681,695 'fetchwithtokenrefresh':667 'file':269,287,329,1332,1506 'file.zip':307 'filemanager.default.moveitem':309 'fileurl':339 'fire':1348 'flow':654,831 'forc':1163,1179,1509 'force-unwrap':1162,1178,1508 'forhttpheaderfield':144,648 'framework':1012 'fromfil':338 'full':577 'func':217,414,423,454,613,630,666,734,847,908,1028 'gain':91 'gap':1276 'genuin':1292 'get':122,446 'guard':191,200,235,460,484 'handl':59,62,655,700,1177,1265,1413,1425 'handleev':385 'handler':113,1423 'handshak':818 'happen':1469 'hardcod':1368,1451 'header':451,602 'hop':1220 'http':170,881,1387,1453 'httperror':712,756 'httprespons':193,237 'httpresponse.statuscode':204,209,244 'httpurlrespons':196,240 'id':1361 'ignor':1229,1386 'imag':1060,1296 'immedi':1124 'implement':403,541,1004,1547,1561 'import':1026 'init':585,1596 'initi':1355 'initialdelay':913,969 'inject':583,1374,1447,1594 'input':1169,1514 'instead':274,1121 'int':714,911,970 'interceptor':512,588 'invalid':471,1402 'invalidrespons':710 'io':1,4,9,15,96 'iso8601':252 'json':212,1193,1403 'jsondecod':250,506,854 'jsonencod':150 'justifi':1278 'keep':1202 'key':761 'keychain':1501 'label':1046 'languag':1100 'larg':268,1199,1331,1459 'lasterror':926,939,976 'let':123,157,192,227,236,248,288,304,317,330,362,400,440,461,485,624,640,841,844,865,867,870,950,1034,1084,1103 'level':188,1312,1614 'librari':1018,1282,1290 'lightweight':550,1584 'line':353,373,378,386 'line-delimit':352 'load':276 'locale.preferredlanguages.first':1101 'localurl':289,311 'locat':299 'log':600,1558 'logic':1485 'long':1233 'long-run':1232 'longer':1284 'loop':1245 'low':1053,1613 'low-level':1612 'main':1196,1216,1476 'mainactor':1222,1471,1532 'major':39 'make':1113 'map':726 'maxattempt':910,932,963 'mean':766 'memori':281,1344 'messag':845 'method':302,444,514,544 'mid':781 'mid-request':780 'middlewar':522,587,589,1377,1449 'miss':1293 'mistak':76,79,1137 'mock':407,1300,1313,1553 'mode':1055 'model':1526 'modern':11 'modifi':1440 'monitor':1035 'monitor.cancel':1042 'monitor.pathupdatehandler':1037 'monitor.start':1043 'move':296 'moya':1261 'multipart':1548 'mutablecompon':478 'mutablecomponents.queryitems':482 'mutablecomponents.url':487 'mv':559 'nativ':92 'need':42,804,1145,1267 'network':2,10,12,41,65,68,102,1005,1011,1027,1119,1235,1349,1364,1417,1467,1524 'network-reach':67 'network.framework':1606 'networkconnectionlost':743,777 'networkerror':706,732,738 'networkerror.httperror':207,686,864,949 'networkerror.invalidresponse':199,247 'networkmonitor':1047 'networkstatusstream':1029 'never':109 'new':107,116 'noconnect':721,745 'notconnectedtointernet':742,768,1429 'nwbrowser':1609 'nwconnect':1607 'nwlisten':1608 'nwpathmonitor':1008,1036,1610 'off-main':1214 'offlin':770,772,1126 'offset':984,1002 'offset-bas':983,1001 'one':1080 'one-off':1079 'oper':917,937 'option':503 'overload':94 'page':993,1496 'pagin':63,64,977,986,1246,1492,1560 'parti':32,1016,1281,1289 'path':441,466,475,476,1120,1538 'path.isconstrained':1052 'path.isexpensive':1049 'pattern':13,560,578,1572,1589,1616 'payload':152,279,1200 'pin':822,1556 'plain':1505 'post':141 'pow':971 'preconditionfailur':470,489 'prefetch':1063 'prepar':614,631 'preview':570,1600 'process':376 'product':1071,1158 'progress':349,1551 'project':117 'proper':1175 'protocol':391,396,411,610,1317 'protocol-bas':390,1316 'proxi':828 'qualiti':1061 'queryitem':448,483 'queryitems.isempty':481 'queue':774,1044 'reachabl':66,69,1006,1017 'reconnect':1577 'reduc':1059 'refer':85,86,1444,1539 'references/background-websocket.md':1563,1564 'references/lightweight-clients.md':573,574,1580,1581 'references/network-framework.md':1603,1604 'references/urlsession-patterns.md':535,536,995,996,1541,1542 'refresh':653,659,1383 'relativeto':456 'reloadignoringlocalcachedata':156 'request':119,136,164,295,324,337,369,525,545,586,591,615,632,638,639,651,782,1082,1350,1372,1432,1557 'request.cachepolicy':155 'request.httpbody':148 'request.httpmethod':140 'request.setvalue':142,645 'request.timeoutinterval':153 'requestmiddlewar':511,611,623 'requir':34,1411 'reserv':1286 'resolvingagainstbaseurl':467 'respect':892,1435 'respond':790 'respons':125,159,165,181,194,229,238,290,319,332,348,364,657,1452,1517,1525 'result':533 'retri':663,776,783,793,883,891,898,1248,1484 'retry/backoff':1554 'return':255,303,496,650,678,692,744,748,752,755,934 'returncachedataelseload':1094 'review':80,83,1414 'review-checklist':82 'run':1234 'second':915 'secureconnectionfail':816 'see':534,572,994,1540,1562,1579,1602 'send':424 'sendabl':413,432,439,612,626,708,840,918,1530 'sensit':1497 'sent':359,595 'server':358,787,833,876,1515 'server-s':357 'session':1069,1104,1115 'setup':549 'show':771,796,807,814 'showerror':875,880 'similar':604 'simpl':1078 'skill':5,6 'skip':897,1062 'source-dpearson2699' 'spike':1345 'sse':383 'state':1226 'static':733 'status':171,528,1388,1395,1454 'statuscod':208,687,713,757,866,882 'store':1442,1499 'strategi':1578 'stream':271,340,347,384,1247,1339 'string':442,445,452,453,629,843,846,1166,1173,1189 'struct':437,579,621,837,1590 'structur':22,701,888,1023,1574 'subclass':1308 'support':571,1601 'surfac':1520 'swap':402 'swift':3,27,120,190,216,282,314,361,410,436,609,620,665,704,836,907,1025,1083 'swiftui':569,1252 'switch':739 't.self':258 't.type':220,417,426,669 'target':26 'task':799,893,1236,1250,1358,1360,1436,1439,1443 'task.checkcancellation':1243 'task.iscancelled':991,1239,1494 'task.sleep':967 'taskiswaitingforconnect':1130 'tcp/udp/websocket':1615 'temp':298 'temporari':286 'test':405,548,1304,1323 'testabl':398,567,1598 'third':31,1015,1280,1288 'third-parti':30,1014,1279,1287 'thread':1197 'throw':178,184,198,206,225,246,421,434,618,628,635,675,920,923,945,959,975 'time':792,1187 'timedout':723,747,749,786,1430 'timeout':1153,1481 'tls':817 'token':641,647,652,661,1370,1375,1446,1498 'tokenprovid':625,644 'tokenstor':672,673 'tokenstore.refreshtoken':691 'track':350 'transform':590 'transport':187,1311 'transport-level':186,1310 'tri':126,149,160,230,256,291,308,320,333,365,371,642,679,689,693,853,935,965,1242 'trigger':829,1363 'true':468,1092,1109 'type':147,219,416,425,668,682,696,703,731,1527 'ui':773,1134,1225,1473,1535 'ui-upd':1534 'unwrap':1164,1180,1510 'updat':1224,1474,1536 'upload':263,315,327,1549 'url':130,138,139,222,223,234,455,458,459,464,472,486,493,497,813,1165,1172,1511 'urlcompon':463 'urlerror':728,736,737,762,764,1427 'urlerror.code':740 'urlprotocol':1307,1552 'urlqueryitem':449 'urlrequest':134,137,517,616,619,633,636 'urlsess':18,35,45,49,71,74,88,90,175,408,505,1065,1105,1129,1150,1210,1263,1270,1301,1477,1567 'urlsession.shared':1073,1141 'urlsession.shared.bytes':367 'urlsession.shared.data':128,162,232 'urlsession.shared.download':293 'urlsession.shared.upload':322,335 'urlsessionconfiguration.default':1086 'usag':859 'use':17,105,110,264,343,557,561,596,887,1007,1140,1171,1249,1258,1269,1306,1315,1327,1335,1357,1419,1461,1531 'user':1522 'userauthenticationrequir':825 'userdefault':1503 'valid':166,168,526,1394,1456 'valuabl':1111 'var':135,443,447,450,477,637,925 'variant':114 'vast':38 'via':584,1376,1438,1448,1595 'view':1354 'wait':1116 'waitsforconnect':1108 'websocket':1571 'without':406 'withretri':909 'wrap':1019","prices":[{"id":"3dbd97d9-deb3-469e-8c52-9bb8a64c8896","listingId":"ea797c62-a2db-413c-8fc3-b4926f6c289f","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T20:33:22.026Z"}],"sources":[{"listingId":"ea797c62-a2db-413c-8fc3-b4926f6c289f","source":"github","sourceId":"dpearson2699/swift-ios-skills/ios-networking","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/ios-networking","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:03.785Z","lastSeenAt":"2026-04-22T00:53:43.456Z"},{"listingId":"ea797c62-a2db-413c-8fc3-b4926f6c289f","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/ios-networking","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/ios-networking","isPrimary":true,"firstSeenAt":"2026-04-18T20:33:22.026Z","lastSeenAt":"2026-04-22T05:40:37.249Z"}],"details":{"listingId":"ea797c62-a2db-413c-8fc3-b4926f6c289f","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"ios-networking","source":"skills_sh","category":"swift-ios-skills","skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/ios-networking"},"updatedAt":"2026-04-22T05:40:37.249Z"}}