{"id":1633,"url":"https://github.com/3lvis/Networking","last_synced_at":"2025-08-02T04:32:03.351Z","repository":{"id":27672952,"uuid":"31158982","full_name":"3lvis/Networking","owner":"3lvis","description":"Swift HTTP Networking with stubbing and caching support","archived":false,"fork":false,"pushed_at":"2025-05-15T10:22:45.000Z","size":11545,"stargazers_count":1369,"open_issues_count":3,"forks_count":114,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-07-24T02:42:57.898Z","etag":null,"topics":["alamofire","carthage","cocoapods","mocking","moya","networking","nsurlsession","stubbing","swift"],"latest_commit_sha":null,"homepage":"","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/3lvis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["3lvis"]}},"created_at":"2015-02-22T09:59:21.000Z","updated_at":"2025-07-19T19:56:45.000Z","dependencies_parsed_at":"2022-09-03T03:42:10.725Z","dependency_job_id":"c8fd680a-89b1-40da-850c-4c2813a67cfd","html_url":"https://github.com/3lvis/Networking","commit_stats":{"total_commits":744,"total_committers":18,"mean_commits":"41.333333333333336","dds":0.5080645161290323,"last_synced_commit":"042aa2d69f1507dbe2a883f86f08c3ac696a80fc"},"previous_names":[],"tags_count":112,"template":false,"template_full_name":null,"purl":"pkg:github/3lvis/Networking","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FNetworking","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FNetworking/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FNetworking/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FNetworking/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/3lvis","download_url":"https://codeload.github.com/3lvis/Networking/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/3lvis%2FNetworking/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268334615,"owners_count":24233793,"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-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","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":["alamofire","carthage","cocoapods","mocking","moya","networking","nsurlsession","stubbing","swift"],"created_at":"2024-01-05T20:15:51.896Z","updated_at":"2025-08-02T04:32:03.338Z","avatar_url":"https://github.com/3lvis.png","language":"Swift","funding_links":["https://github.com/sponsors/3lvis"],"categories":["Networking","HarmonyOS"],"sub_categories":["Video","Other free courses","Windows Manager"],"readme":"![Networking](https://raw.githubusercontent.com/3lvis/Networking/1702d0e4575947ad12583b8f94a5ba1953804efc/.github/cover-v3.png)\n\n**Networking** was born out of the necessity of having a networking library that has a straightforward API that supports faking requests and caching images out of the box.\n\n- Friendly API\n- Singleton free\n- No external dependencies\n- Minimal implementation\n- Fully unit tested\n- Simple request cancellation\n- Fake requests easily (mocking/stubbing)\n- Flexible caching\n- Image downloading\n\n## Table of Contents\n\n* [Choosing a configuration](#choosing-a-configuration)\n* [Changing request headers](#changing-request-headers)\n* [Authenticating](#authenticating)\n    * [HTTP basic](#http-basic)\n    * [Bearer token](#bearer-token)\n    * [Custom authentication header](#custom-authentication-header)\n* [Making a request](#making-a-request)\n  * [The basics](#the-basics)\n  * [The Result type](#the-result-type)\n* [Choosing a content or parameter type](#choosing-a-content-or-parameter-type)\n    * [JSON](#json)\n    * [URL-encoding](#url-encoding)\n    * [Multipart](#multipart)\n    * [Others](#others)\n* [Cancelling a request](#cancelling-a-request)\n* [Faking a request](#faking-a-request)\n* [Downloading and caching an image](#downloading-and-caching-an-image)\n* [Logging errors](#logging-errors)\n* [Updating the Network Activity Indicator](#updating-the-network-activity-indicator)\n* [Installing](#installing)\n* [Author](#author)\n* [License](#license)\n* [Attribution](#attribution)\n\n## Choosing a configuration\n\nInitializing an instance of **Networking** means you have to select a [URLSessionConfiguration](https://developer.apple.com/documentation/foundation/urlsessionconfiguration). The available types are `.default`, `.ephemeral` and `.background`, if you don't provide any or don't have special needs then `default` will be used.\n\n - `.default`: The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain.\n\n- `.ephemeral`: An ephemeral session configuration object is similar to a default session configuration object except that the corresponding session object does not store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file. The main advantage to using ephemeral sessions is privacy. By not writing potentially sensitive data to disk, you make it less likely that the data will be intercepted and used later. For this reason, ephemeral sessions are ideal for private browsing modes in web browsers and other similar situations.\n\n- `.background`: This configuration type is suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.\n\n```swift\n// Default\nlet networking = Networking(baseURL: \"http://httpbin.org\")\n\n// Ephemeral\nlet networking = Networking(baseURL: \"http://httpbin.org\", configuration: .ephemeral)\n```\n\n## Changing request headers\n\nYou can set the `headerFields` in any networking object.\n\nThis will append (if not found) or overwrite (if found) what NSURLSession sends on each request.\n\n```swift\nnetworking.headerFields = [\"User-Agent\": \"your new user agent\"]\n```\n\n## Authenticating\n\n### HTTP basic\n\nTo authenticate using [basic authentication](http://www.w3.org/Protocols/HTTP/1.0/spec.html#BasicAA) with a username **\"aladdin\"** and password **\"opensesame\"** you only need to do this:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nnetworking.setAuthorizationHeader(username: \"aladdin\", password: \"opensesame\")\nlet result = try await networking.oldGet(\"/basic-auth/aladdin/opensesame\")\n// Successfully authenticated!\n```\n\n### Bearer token\n\nTo authenticate using a [bearer token](https://tools.ietf.org/html/rfc6750) **\"AAAFFAAAA3DAAAAAA\"** you only need to do this:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nnetworking.setAuthorizationHeader(token: \"AAAFFAAAA3DAAAAAA\")\nlet result = try await networking.oldGet(\"/get\")\n// Successfully authenticated!\n```\n\n### Custom authentication header\n\nTo authenticate using a custom authentication header, for example **\"Token token=AAAFFAAAA3DAAAAAA\"** you would need to set the following header field: `Authorization: Token token=AAAFFAAAA3DAAAAAA`. Luckily, **Networking** provides a simple way to do this:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nnetworking.setAuthorizationHeader(headerValue: \"Token token=AAAFFAAAA3DAAAAAA\")\nlet result = try await networking.oldGet(\"/get\")\n// Successfully authenticated!\n```\n\nProviding the following authentication header `Anonymous-Token: AAAFFAAAA3DAAAAAA` is also possible:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nnetworking.setAuthorizationHeader(headerKey: \"Anonymous-Token\", headerValue: \"AAAFFAAAA3DAAAAAA\")\nlet result = try await networking.oldGet(\"/get\")\n// Successfully authenticated!\n```\n\n## Making a request\n\n### The basics\n\nMaking a request is as simple as just calling `get`, `post`, `put`, or `delete`.\n\n**GET example**:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldGet(\"/get\")\nswitch result {\ncase .success(let response):\n    let json = response.dictionaryBody\n    // Do something with JSON, you can also get arrayBody\ncase .failure(let response):\n    // Handle error\n}\n```\n\n**POST example**:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldPost(\"/post\", parameters: [\"username\" : \"jameson\", \"password\" : \"secret\"])\n /*\n {\n     \"json\" : {\n         \"username\" : \"jameson\",\n         \"password\" : \"secret\"\n     },\n     \"url\" : \"http://httpbin.org/post\",\n     \"data\" : \"{\"password\" : \"secret\",\"username\" : \"jameson\"}\",\n     \"headers\" : {\n         \"Accept\" : \"application/json\",\n         \"Content-Type\" : \"application/json\",\n         \"Host\" : \"httpbin.org\",\n         \"Content-Length\" : \"44\",\n         \"Accept-Language\" : \"en-us\"\n     }\n }\n */\n```\n\nYou can get the response headers inside the success.\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldGet(\"/get\")\nswitch result {\ncase .success(let response):\n    let headers = response.allHeaderFields\n    // Do something with headers\ncase .failure(let response):\n    // Handle error\n}\n```\n\n### The NetworkingResult type\n\nThe [NetworkingResult](https://github.com/3lvis/Networking/blob/master/Sources/NetworkingResult.swift) type is an enum that has two cases: `success` and `failure`. The `success` case has a response, the `failure` case has an error and a response, none of these ones are optionals.\n\nHere's how to use it:\n\n```swift\n// The best way\nlet networking = Networking(baseURL: \"http://fakerecipes.com\")\nlet result = try await networking.oldGet(\"/recipes\")\nswitch result {\ncase .success(let response):\n    // We know we'll be receiving an array with the best recipes, so we can just do:\n    let recipes = response.arrayBody // BOOM, no optionals. [[String: Any]]\n\n    // If we need headers or response status code we can use the HTTPURLResponse for this.\n    let headers = response.headers // [String: Any]\ncase .failure(let response):\n    // Non-optional error ✨\n    let errorCode = response.error.code\n\n    // Our backend developer told us that they will send a json with some\n    // additional information on why the request failed, this will be a dictionary.\n    let json = response.dictionaryBody // BOOM, no optionals here [String: Any]\n\n    // We want to know the headers of the failed response.\n    let headers = response.headers // [String: Any]\n}\n```\n\nAnd that's how we do things in **Networking** without optionals.\n\n## Choosing a Content or Parameter Type\n\nThe `Content-Type` HTTP specification is so unfriendly, you have to know the specifics of it before understanding that content type is really just the parameter type. Because of this **Networking** uses a `ParameterType` instead of a `ContentType`. Anyway, here's hoping this makes it more human friendly.\n\n### JSON\n\n**Networking** by default uses `application/json` as the `Content-Type`, if you're sending JSON you don't have to do anything. But if you want to send other types of parameters you can do it by providing the `ParameterType` attribute.\n\nWhen sending JSON your parameters will be serialized to data using `NSJSONSerialization`.\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldPost(\"/post\", parameters: [\"name\" : \"jameson\"])\n// Successfull post using `application/json` as `Content-Type`\n```\n\n### URL-encoding\n\n If you want to use `application/x-www-form-urlencoded` just use the `.formURLEncoded` parameter type, internally **Networking** will format your parameters so they use [`Percent-encoding` or `URL-enconding`](https://en.wikipedia.org/wiki/Percent-encoding#The_application.2Fx-www-form-urlencoded_type).\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldPost(\"/post\", parameterType: .formURLEncoded, parameters: [\"name\" : \"jameson\"])\n// Successfull post using `application/x-www-form-urlencoded` as `Content-Type`\n```\n\n### Multipart\n\n**Networking** provides a simple model to use `multipart/form-data`. A multipart request consists in appending one or several [FormDataPart](https://github.com/3lvis/Networking/blob/master/Sources/FormDataPart.swift) items to a request. The simplest multipart request would look like this.\n\n```swift\nlet networking = Networking(baseURL: \"https://example.com\")\nlet imageData = UIImagePNGRepresentation(imageToUpload)!\nlet part = FormDataPart(data: imageData, parameterName: \"file\", filename: \"selfie.png\")\nlet result = try await networking.oldPost(\"/image/upload\", part: part)\n// Successfull upload using `multipart/form-data` as `Content-Type`\n```\n\nIf you need to use several parts or append other parameters than aren't files, you can do it like this:\n\n```swift\nlet networking = Networking(baseURL: \"https://example.com\")\nlet part1 = FormDataPart(data: imageData1, parameterName: \"file1\", filename: \"selfie1.png\")\nlet part2 = FormDataPart(data: imageData2, parameterName: \"file2\", filename: \"selfie2.png\")\nlet parameters = [\"username\" : \"3lvis\"]\nlet result = try await networking.oldPost(\"/image/upload\", parts: [part1, part2], parameters: parameters)\n// Do something\n```\n\n**FormDataPart Content-Type**:\n\n`FormDataPart` uses `FormDataPartType` to generate the `Content-Type` for each part. The default `FormDataPartType` is `.Data` which adds the `application/octet-stream` to your part. If you want to use a `Content-Type` that is not available between the existing `FormDataPartType`s, you can use `.Custom(\"your-content-type)`.\n\n### Others\n\nAt the moment **Networking** supports four types of `ParameterType`s out of the box: `JSON`, `FormURLEncoded`, `MultipartFormData` and `Custom`. Meanwhile `JSON` and `FormURLEncoded` serialize your parameters in some way, `Custom(String)` sends your parameters as plain `NSData` and sets the value inside `Custom` as the `Content-Type`.\n\nFor example:\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldPost(\"/upload\", parameterType: .Custom(\"application/octet-stream\"), parameters: imageData)\n// Successfull upload using `application/octet-stream` as `Content-Type`\n```\n\n## Cancelling a request\n\n### Using path\n\nCancelling any request for a specific path is really simple. Beware that cancelling a request will cause the request to return with an error with status code URLError.cancelled.\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.oldGet(\"/get\")\n// Cancelling a GET request returns an error with code URLError.cancelled which means cancelled request\n\n// In another place\nnetworking.cancelGET(\"/get\")\n```\n\n## Faking a request\n\nFaking a request means that after calling this method on a specific path, any call to this resource, will return what you registered as a response. This technique is also known as mocking or stubbing.\n\n**Faking with successfull response**:\n\n```swift\nlet networking = Networking(baseURL: \"https://api-news.layervault.com/api/v2\")\nnetworking.fakeGET(\"/stories\", response: [[\"id\" : 47333, \"title\" : \"Site Design: Aquest\"]])\nlet result = try await networking.oldGet(\"/stories\")\n// JSON containing stories\n```\n\n**Faking with contents of a file**:\n\nIf your file is not located in the main bundle you have to specify using the bundle parameters, otherwise `NSBundle.mainBundle()` will be used.\n\n```swift\nlet networking = Networking(baseURL: baseURL)\nnetworking.fakeGET(\"/entries\", fileName: \"entries.json\")\nlet result = try await networking.oldGet(\"/entries\")\n// JSON with the contents of entries.json\n```\n\n**Faking with status code**:\n\nIf you do not provide a status code for this fake request, the default returned one will be 200 (SUCCESS), but if you do provide a status code that is not 2XX, then **Networking** will return an NSError containing the status code and a proper error description.\n\n```swift\nlet networking = Networking(baseURL: \"https://api-news.layervault.com/api/v2\")\nnetworking.fakeGET(\"/stories\", response: nil, statusCode: 500)\nlet result = try await networking.oldGet(\"/stories\")\n// error with status code 500\n```\n\n## Downloading and caching an image\n\n**Downloading**:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.downloadImage(\"/image/png\")\n// Do something with the downloaded image\n```\n\n**Cancelling**:\n\n```swift\nlet networking = Networking(baseURL: baseURL)\nlet result = try await networking.downloadImage(\"/image/png\")\n// Cancelling an image download returns an error with code URLError.cancelled which means cancelled request\n\nnetworking.cancelImageDownload(\"/image/png\")\n```\n\n**Caching**:\n\n**Networking** uses a multi-cache architecture when downloading images, the first time the `downloadImage` method is called for a specific path, it will store the results in disk (Documents folder) and in memory (NSCache), so in the next call it will return the cached results without hitting the network.\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet result = try await networking.downloadImage(\"/image/png\")\n// Image from network\n   \nlet result = try await networking.downloadImage(\"/image/png\")\n// Image from cache\n```\n\nIf you want to remove the downloaded image you can do it like this:\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nlet destinationURL = try networking.destinationURL(for: \"/image/png\")\nif let path = destinationURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {\n   try NSFileManager.defaultManager().removeItemAtPath(path)\n}\n```\n\n**Faking**:\n\n```swift\nlet networking = Networking(baseURL: baseURL)\nlet pigImage = UIImage(named: \"pig.png\")!\nnetworking.fakeImageDownload(\"/image/png\", image: pigImage)\nlet result = try await networking.downloadImage(\"/image/png\")\n// Here you'll get the provided pig.png image\n```\n\n## Logging errors\n\nAny error catched by **Networking** will be printed in your console. This is really convenient since you want to know why your networking call failed anyway.\n\nFor example a cancelled request will print this:\n\n```shell\n========== Networking Error ==========\n\nCancelled request: https://api.mmm.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small\n\n================= ~ ==================\n```\n\nA 404 request will print something like this:\n\n```shell\n========== Networking Error ==========\n\n*** Request ***\n\nError 404: Error Domain=NetworkingErrorDomain Code=404 \"not found\" UserInfo={NSLocalizedDescription=not found}\n\nURL: http://httpbin.org/posdddddt\n\nHeaders: [\"Accept\": \"application/json\", \"Content-Type\": \"application/json\"]\n\nParameters: {\n  \"password\" : \"secret\",\n  \"username\" : \"jameson\"\n}\n\nData: \u003c!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"\u003e\n\u003ctitle\u003e404 Not Found\u003c/title\u003e\n\u003ch1\u003eNot Found\u003c/h1\u003e\n\u003cp\u003eThe requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.\u003c/p\u003e\n\n\n*** Response ***\n\nHeaders: [\"Content-Length\": 233, \"Server\": nginx, \"Access-Control-Allow-Origin\": *, \"Content-Type\": text/html, \"Date\": Sun, 29 May 2016 07:19:13 GMT, \"Access-Control-Allow-Credentials\": true, \"Connection\": keep-alive]\n\nStatus code: 404 — not found\n\n================= ~ ==================\n```\n\nTo disable error logging use the flag `disableErrorLogging`.\n\n```swift\nlet networking = Networking(baseURL: \"http://httpbin.org\")\nnetworking.disableErrorLogging = true\n```\n\n## Installing\n\n**Networking** is available through Swift Package Manager.\n\n## Author\n\nThis library was made with love by [@3lvis](https://twitter.com/3lvis).\n\n\n## License\n\n**Networking** is available under the MIT license. See the [LICENSE file](https://github.com/3lvis/Networking/blob/master/LICENSE.md) for more info.\n\n\n## Attribution\n\nThe logo typeface comes thanks to [Sanid Jusić](https://dribbble.com/shots/1049674-Free-Colorfull-Triangle-Typeface).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3lvis%2FNetworking","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F3lvis%2FNetworking","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F3lvis%2FNetworking/lists"}