{"id":13995456,"url":"https://github.com/radianttap/Alley","last_synced_at":"2025-07-22T21:33:06.426Z","repository":{"id":144987906,"uuid":"226860665","full_name":"radianttap/Alley","owner":"radianttap","description":"Essential `URLSessionDataTask` micro-wrapper for communication with HTTP(S) web services, with built-in automatic request retries.","archived":false,"fork":false,"pushed_at":"2024-10-25T10:35:00.000Z","size":115,"stargazers_count":151,"open_issues_count":0,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-07-09T00:39:28.122Z","etag":null,"topics":["ios","macos","networking","tvos","urlsession","urlsessiondatatask","watchos"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/radianttap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-12-09T11:58:45.000Z","updated_at":"2025-07-07T22:12:20.000Z","dependencies_parsed_at":"2024-04-23T10:57:53.527Z","dependency_job_id":null,"html_url":"https://github.com/radianttap/Alley","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/radianttap/Alley","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radianttap%2FAlley","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radianttap%2FAlley/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radianttap%2FAlley/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radianttap%2FAlley/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/radianttap","download_url":"https://codeload.github.com/radianttap/Alley/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radianttap%2FAlley/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266578642,"owners_count":23951148,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","macos","networking","tvos","urlsession","urlsessiondatatask","watchos"],"created_at":"2024-08-09T14:03:25.201Z","updated_at":"2025-07-22T21:33:06.117Z","avatar_url":"https://github.com/radianttap.png","language":"Swift","readme":"[![](https://img.shields.io/github/tag/radianttap/Alley.svg?label=current)](https://github.com/radianttap/Alley/releases)\n[![](https://img.shields.io/github/license/radianttap/Alley.svg)](https://github.com/radianttap/Alley/blob/master/LICENSE)\n![](https://img.shields.io/badge/swift-6.0-223344.svg?logo=swift\u0026labelColor=FA7343\u0026logoColor=white)\n\\\n![platforms: iOS|tvOS|watchOS|macOS|visionOS](https://img.shields.io/badge/platform-iOS_15_·_tvOS_15_·_watchOS_10_·_macOS_12_·_visionOS_1-blue.svg)\n\n# Alley\n\nEssential `URLSessionDataTask` micro-wrapper for communication with HTTP(S) web services. \n\n## Why\n\nIn most cases where you need to fetch something from the internet, you:\n\n1. Want to get the data at the URL you are targeting, no matter what\n2. In case when it’s simply not possible, display some useful error to the end-customer *and* display / log what error actually happened so you can troubleshoot and debug\n\nSecond point is nice to have. First one is vastly more important since that data is the reason you are doing this at all.\n\n\u003e Thus main feature of Alley is **automatic request retries** for predefined conditions.\n\n## Integration\n\nJust drag `Alley` folder into your project.\n\nOr just add this repo’s URL through Swift Package Manager.\n\n- Version 2.x supports old school stuff with completion handlers.\n- Version 3.x is pure `async`/`await`.\n- Version 4.x has strict concurrency checking turned ON and Swift 6 language mode.\n\n## Usage\n\nYou would already have some `URLSession` instance to work with. Then instead of this:\n\n```swift\nlet urlRequest = URLRequest(...)\n\ndo {\n\tlet data = try await urlSession.data(for: urlRequest)\n} catch let err {\n\t//...process error\n}\n```\n\nwith _Alley_ you will do this:\n\n```swift\nlet urlRequest = URLRequest(...)\n\ndo {\n\tlet data = try await urlSession.alleyData(for: urlRequest)\n} catch let err {\n\t//...process NetworkError\n}\n```\n\nIn case the request was successful, you would get the `Data` instance returned from the service which you can convert into whatever you expected it to be.\n\nIn case of failure you will get an instance of `NetworkError`.\n\n### NetworkError\n\nThis is custom Error (implemented by an enum) which – for starters – wraps stuff returned by `URLSessionDataTask`. Thus first few possible options are:\n\n```swift\n///\t`URLSession` errors are passed-through, handle as appropriate.\ncase urlError(URLError)\n\n///\tURLSession returned an `Error` object which is not `URLError`\ncase generalError(Swift.Error)\n```\n\nNext, if the returned `URLResponse` is not `HTTPURLResponse`:\n\n```swift\ncase invalidResponseType(URLResponse)\n```\n\nNow, if it is `HTTPURLResponse` but status code is `400` or higher, this is an error returned by the web service endpoint you are communicating with. Hence you get the entire `HTTPURLResponse` and `Data` (if it exists) so caller can figure out what happened.\n\n```swift\ncase endpointError(HTTPURLResponse, Data?)\n```\n\nIn the calling object, you can use these values and try to build instances of strongly-typed custom errors related to the given specific web service.\n\nIf status code is in `2xx` range, you may have a case of missing response body. \n\n```swift\ncase noResponseData(HTTPURLResponse)\n```\n\nThis may or may not be an error. If you perform `PUT` or `DELETE` or even `POST` requests, your service may not return any data as valid response (just `200 OK` or whatever). In that case, prevent this error by calling perform like this:\n\n```swift\nlet urlRequest = URLRequest(...)\n\nlet data = try await urlSession.alleyData(for: urlRequest, allowEmptyData: true)\n```\n\nwhere you will get empty `Data()`.\n\nThere’s one more possible `NetworkError` value, which is related to...\n\n## Automatic retries\n\nDefault number of retries is `10`.\n\nThis value is automatically used for all networking calls but you can adjust it per call by simply supplying appropriate number to `maxRetries` argument:\n\n```swift\nlet urlRequest = URLRequest(...)\n\nlet data = try await urlSession.alleyData(for: urlRequest, maxRetries: 5)\n```\n\nHow automatic retries work? \n\nIn case of a `NetworkError` being raised, _Alley_ will check its `shouldRetry` property and – if `true` – it will increment retry counter by 1 and perform `URLSessionDataTask` again. And again. And again...until it reaches `maxRetries` value when it will return `NetworkError.inaccessible` as result.\n\nEach retry is delayed by half a second but you can supply any value you want (including `0`) in the call to `alleyData`, argument `retryInterval`.\n\n```swift\nlet urlRequest = URLRequest(...)\n\nlet data = try await urlSession.alleyData(for: urlRequest, retryInterval: 0.3)\n```\n\nYou can customize the behavior by changing the implementation of `shouldRetry` property (in this case I recommend to manually copy Alley folder into your project).\n\n* * *\n\nThat’s about it. _Alley_ is intentionally simple to encourage writing as little code as possible, hiding away often-repeated boilerplate.\n\n## License\n\n[MIT License,](https://github.com/radianttap/Alley/blob/v2/LICENSE) like all my open source code.\n\n## Give back\n\nIf you found this code useful, please consider [buying me a coffee](https://www.buymeacoffee.com/radianttap) or two. ☕️😋\n","funding_links":["https://www.buymeacoffee.com/radianttap"],"categories":["Swift"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradianttap%2FAlley","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fradianttap%2FAlley","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradianttap%2FAlley/lists"}