{"id":1661,"url":"https://github.com/billp/TermiNetwork","last_synced_at":"2025-08-06T14:31:13.870Z","repository":{"id":45233882,"uuid":"121552352","full_name":"billp/TermiNetwork","owner":"billp","description":"🌏 A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.","archived":false,"fork":false,"pushed_at":"2023-08-29T09:44:14.000Z","size":56209,"stargazers_count":99,"open_issues_count":3,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-11-24T04:23:48.969Z","etag":null,"topics":["apple","carthage","cocoapods","deserialization","interceptors","ios","library","macos","mock-data","networking","routers","swift","swift-packages","swiftui","transformers","tvos","watchos","xcode"],"latest_commit_sha":null,"homepage":"https://billp.github.io/TermiNetwork","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/billp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2018-02-14T19:31:25.000Z","updated_at":"2024-06-12T21:35:32.000Z","dependencies_parsed_at":"2024-01-29T17:08:21.466Z","dependency_job_id":null,"html_url":"https://github.com/billp/TermiNetwork","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/billp%2FTermiNetwork","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/billp%2FTermiNetwork/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/billp%2FTermiNetwork/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/billp%2FTermiNetwork/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/billp","download_url":"https://codeload.github.com/billp/TermiNetwork/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228634104,"owners_count":17949189,"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","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":["apple","carthage","cocoapods","deserialization","interceptors","ios","library","macos","mock-data","networking","routers","swift","swift-packages","swiftui","transformers","tvos","watchos","xcode"],"created_at":"2024-01-05T20:15:52.607Z","updated_at":"2024-12-09T14:31:15.433Z","avatar_url":"https://github.com/billp.png","language":"Swift","readme":"\u003cp\u003e\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/billp/TermiNetwork/master/TermiNetworkLogo.svg\" alt=\"\" data-canonical-src=\"\" width=\"500rem\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\u003cb\u003e A zero-dependency networking solution for building modern and secure iOS, watchOS, macOS and tvOS applications.\u003c/b\u003e\n  \u003cbr /\u003e\u003cbr /\u003e\n  \u003cimg src=\"https://github.com/billp/TermiNetwork/actions/workflows/main.yml/badge.svg\" /\u003e\n  \u003cimg src=\"https://img.shields.io/cocoapods/v/TermiNetwork.svg?style=flat\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-green\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Language-Swift 5.3-blue\" /\u003e\n  \u003cimg src=\"https://img.shields.io/github/license/billp/TermiNetwork\" /\u003e\n  \u003cimg src=\"https://img.shields.io/cocoapods/p/TermiNetwork\" /\u003e\n  \u003ca href=\"https://codecov.io/gh/billp/TermiNetwork\"\u003e\n    \u003cimg src=\"https://codecov.io/gh/billp/TermiNetwork/branch/master/graph/badge.svg?token=QI9KOV99AG\" /\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://billp.github.io/TermiNetwork/badge.svg\" /\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n🚀 \u003ci\u003e\u003cb\u003eTermiNetwork\u003c/b\u003e has been tested in a production environment with a heavy load of asynchronous requests and tens of thousands of unique clients per day\u003c/i\u003e.\n\u003cbr /\u003e\u003cbr /\u003e\n\n## Features\n- [x] Multi-environment setup \u003cbr /\u003e\n- [x] Model deserialization with \u003cb\u003eCodables\u003c/b\u003e\u003cbr /\u003e\n- [x] Async await support\u003cbr /\u003e\n- [x] Decodes response to the given type: \u003cb\u003eCodable\u003c/b\u003e, \u003cb\u003eTransformer\u003c/b\u003e, \u003cb\u003eUIImage\u003c/b\u003e, \u003cb\u003eData\u003c/b\u003e or \u003cb\u003eString\u003c/b\u003e\u003cbr /\u003e\n- [x] \u003cb\u003eUIKit\u003c/b\u003e/\u003cb\u003eSwiftUI\u003c/b\u003e extensions for downloading remote images\u003cbr /\u003e\n- [x] Request grouping with Repositories\u003cbr /\u003e\n- [x] Detects network status with Reachability\u003cbr /\u003e\n- [x] Transformers: convert models from one type to another easily\u003cbr /\u003e\n- [x] Error Handling\u003cbr /\u003e\n- [x] Interceptors\u003cbr /\u003e\n- [x] Request mocking\u003cbr /\u003e\n- [x] Certificate Pinning\u003cbr /\u003e\n- [x] Flexible configuration\u003cbr /\u003e\n- [x] Middleware support\u003cbr /\u003e\n- [x] File/Data Upload/Download support\u003cbr /\u003e\n- [x] Pretty printed debug information\n\n### Table of contents\n- [Installation](#installation)\n- [Demo Application](#demo_app)\n- [Usage](#usage)\n  - [Simple usage of \u003cb\u003eRequest\u003c/b\u003e](#simple_usage)\n  - [Advanced usage of \u003cb\u003eRequest\u003c/b\u003e with \u003cb\u003eConfiguration\u003c/b\u003e and custom \u003cb\u003eQueue\u003c/b\u003e](#advanced_usage)\n  - [Complete project setup with \u003cb\u003eEnvironments\u003c/b\u003e and \u003cb\u003eRepositories\u003c/b\u003e (Recommended)](#complete_setup)\n\t  - [Environment setup](#setup_environments)\n\t  - [Repository setup](#setup_repositories)\n\t  - [Make a request](#construct_request)\n- [Queue Hooks](#queue_hooks)\n- [File/Data Upload](#file_upload)\n- [File Download](#file_download)\n- [Error Handling](#error_handling)\n- [Cancelling a Request](#request_cancelling)\n- [Reachability](#reachability)\n- [Transformers](#transformers)\n- [Mock responses](#mock_responses)\n- [Interceptors](#interceptors)\n- [Image Helpers](#image_helpers)\n\t- [SwiftUI Image Helper](#swift_ui_image_helper)\n\t- [UIImageView, NSImageView, WKInterfaceImage Extensions](#image_extensions)\n- [Middleware](#middleware)\n- [Debug Logging](#debug_logging)\n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n\n## Installation\nYou can install **TermiNetwork** with one of the following ways...\n### CocoaPods\n\nAdd the following line to your **Podfile** and run **pod install** in your terminal:\n```ruby\npod 'TermiNetwork', '~\u003e 4.0'\n```\n\n### Carthage\n\nAdd the following line to your **Carthage** and run **carthage update** in your terminal:\n```ruby\ngithub \"billp/TermiNetwork\" ~\u003e 4.0\n```\n\n### Swift Package Manager\n\nGo to **File** \u003e **Swift Packages** \u003e **Add Package Dependency** and add the following URL:\n```\nhttps://github.com/billp/TermiNetwork\n```\n\n\u003ca name=\"demo_app\"\u003e\u003c/a\u003e\n\n## Demo Application\nTo see all the features of TermiNetwork in action, download the source code and run the **TermiNetworkExamples** scheme.\n\n\u003ca name=\"usage\"\u003e\u003c/a\u003e\n\n## Usage\n\n\u003ca name=\"simple_usage\"\u003e\u003c/a\u003e\n\n### Simple usage (Request)\n\nLet's say you have the following Codable model:\n\n```swift\nstruct Todo: Codable {\n   let id: Int\n   let title: String\n}\n```\n\nThe following example creates a request that adds a new Todo:\n\n```swift\nlet params = [\"title\": \"Go shopping.\"]\nlet headers = [\"x-auth\": \"abcdef1234\"]\n\nRequest(method: .get,\n\turl: \"https://myweb.com/api/todos\",\n\theaders: headers,\n\tparams: params)\n    .success(responseType: Todo.self) { todos in\n\tprint(todos)\n    }\n    .failure { error in\n\tprint(error.localizedDescription)\n    }\n```\n\nor with **async await**:\n\n```swift\nlet request = Request(method: .get, \n                      url: \"https://myweb.com/api/todos\", \n                      headers: headers, \n                      params: params)\n\ndo {\n    let todos: [Todo] = try await request.async()\n    print(todos)\n} catch let error { \n    print(error.localizedDescription)\n}\n```\n\n#### Parameters Explanation\n\n##### method\nOne of the following supported HTTP methods:\n```\n.get, .head, .post, .put, .delete, .connect, .options, .trace or .patch\n```\n\n##### responseType\nOne of the following supported response types\n```\nCodable.self (implementations), UIImage.self, Data.self or String.self\n```\n\n##### onSuccess\nA callback that returns an object of the given type. (specified in responseType parameter)\n\n##### onFailure\nA callback that returns a **Error** and the response **Data** (if any).\n\n\u003ca name=\"advanced_usage\"\u003e\u003c/a\u003e\n\n### Advanced usage of Request with Configuration and custom Queue\n\nThe following example uses a custom **Queue** with **maxConcurrentOperationCount** and a configuration object. To see the full list of available configuration properties, take a look at \u003ca href=\"https://billp.github.io/TermiNetwork/Classes/Configuration.html#/Public%20properties\" target=\"_blank\"\u003eConfiguration properties\u003c/a\u003e in documentation.\n\n```swift\nlet myQueue = Queue(failureMode: .continue)\nmyQueue.maxConcurrentOperationCount = 2\n\nlet configuration = Configuration(\n    cachePolicy: .useProtocolCachePolicy,\n    timeoutInterval: 30,\n    requestBodyType: .JSON\n)\n\nlet params = [\"title\": \"Go shopping.\"]\nlet headers = [\"x-auth\": \"abcdef1234\"]\n\nRequest(method: .post,\n        url: \"https://myweb.com/todos\",\n        headers: headers,\n        params: params,\n        configuration: configuration)\n    .queue(queue)\n    .success(responseType: String.self) { response in\n        print(response)\n    }\n    .failure { error in\n        print(error.localizedDescription)\n    }\n```\n\nor with **async await**:\n\n```swift\ndo {\n    let response = try Request(\n    \tmethod: .post,\n\turl: \"https://myweb.com/todos\",\n\theaders: headers,\n\tparams: params,\n\tconfiguration: configuration\n    )\n    .queue(queue)\n    .async(as: String.self)\n} catch let error {\n    print(error.localizedDescription)\n}\n```\n\n\nThe request above uses a custom queue **myQueue** with a failure mode of **.continue** (default), which means that the queue continues its execution if a request fails.\n\n\u003ca name=\"complete_setup\"\u003e\u003c/a\u003e\n\n## Complete setup with \u003cb\u003eEnvironments\u003c/b\u003e and \u003cb\u003eRepositories\u003c/b\u003e\nThe complete and recommended setup of TermiNetwork consists of defining **Environments** and **Repositories**.  \n\n\u003ca name=\"setup_environments\"\u003e\u003c/a\u003e\n\n#### Environment setup\nCreate a swift **enum** that implements the **EnvironmentProtocol** and define your environments.\n\n##### Example\n```swift\nenum MyAppEnvironments: EnvironmentProtocol {\n    case development\n    case qa\n\n    func configure() -\u003e Environment {\n        switch self {\n        case .development:\n            return Environment(scheme: .https,\n                               host: \"localhost\",\n                               suffix: .path([\"v1\"]),\n                               port: 3000)\n        case .qa:\n            return Environment(scheme: .http,\n                               host: \"myqaserver.com\",\n                               suffix: .path([\"v1\"]))\n        }\n    }\n}\n```\n*Optionally you can  pass a **configuration** object to make all Repositories and Endpoints to inherit the given configuration settings.*\n\nTo set your global environment use Environment.set method\n```swift\nEnvironment.set(MyAppEnvironments.development)\n```\n\n\u003ca name=\"setup_repositories\"\u003e\u003c/a\u003e\n\n#### Repository setup\nCreate a swift **enum** that implements the **EndpointProtocol** and define your endpoints.\n\nThe following example creates a TodosRepository with all the required endpoints as cases.\n\n##### Example\n```swift\nenum TodosRepository: EndpointProtocol {\n    // Define your endpoints\n    case list\n    case show(id: Int)\n    case add(title: String)\n    case remove(id: Int)\n    case setCompleted(id: Int, completed: Bool)\n\n    static let configuration = Configuration(requestBodyType: .JSON,\n                                             headers: [\"x-auth\": \"abcdef1234\"])\n\n\n    // Set method, path, params, headers for each endpoint\n    func configure() -\u003e EndpointConfiguration {\n\n        switch self {\n        case .list:\n            return .init(method: .get,\n                         path: .path([\"todos\"]), // GET /todos\n                         configuration: Self.configuration)\n        case .show(let id):\n            return .init(method: .get,\n                         path: .path([\"todo\", String(id)]), // GET /todos/[id]\n                         configuration: Self.configuration)\n        case .add(let title):\n            return .init(method: .post,\n                         path: .path([\"todos\"]), // POST /todos\n                         params: [\"title\": title],\n                         configuration: Self.configuration)\n        case .remove(let id):\n            return .init(method: .delete,\n                         path: .path([\"todo\", String(id)]), // DELETE /todo/[id]\n                         configuration: configuration)\n        case .setCompleted(let id, let completed):\n            return .init(method: .patch,\n                         path: .path([\"todo\", String(id)]), // PATCH /todo/[id]\n                         params: [\"completed\": completed],\n                         configuration: configuration)\n        }\n    }\n}\n\n```\nYou can optionally pass a **configuration** object to each case if you want provide different configuration for each endpoint.\n\n\u003ca name=\"construct_request\"\u003e\u003c/a\u003e\n\n#### Make a request\nTo create the request you have to initialize a **Client** instance and specialize it with your defined Repository, in our case **TodosRepository**:\n```swift\nClient\u003cTodosRepository\u003e().request(for: .add(title: \"Go shopping!\"))\n    .success(responseType: Todo.self) { todo in\n        // do something with todo\n    }\n    .failure { error in\n        // do something with error\n    }\n```\n\nor with **async await**\n\n```swift\ndo {\n    let toto: Todo = Client\u003cTodosRepository\u003e()\n\t.request(for: .add(title: \"Go shopping!\"))\n\t.async()\n} catch let error {\n    print(error.localizedDescription)\n}\n```\n\n\u003ca name=\"queue_hooks\"\u003e\u003c/a\u003e\n\n## Queue Hooks\nHooks are closures that  run before and/or after a request execution in a queue. The following hooks are available:\n\n```swift\nQueue.shared.beforeAllRequestsCallback = {\n    // e.g. show progress loader\n}\n\nQueue.shared.afterAllRequestsCallback = { completedWithError in\n    // e.g. hide progress loader\n}\n\nQueue.shared.beforeEachRequestCallback = { request in\n    // do something with request\n}\n\nQueue.shared.afterEachRequestCallback = { request, data, urlResponse, error\n    // do something with request, data, urlResponse, error\n}\n```\n\nFor more information take a look at \u003ca href=\"https://billp.github.io/TermiNetwork/Classes/Queue.html\" target=\"_blank\"\u003eQueue\u003c/a\u003e in documentation.\n\n\u003ca name=\"file_upload\"\u003e\u003c/a\u003e\n\n## File/Data Upload\n\nYou can use the **.upload**, **.asyncUpload** methods of a Request object to start an upload operation. The upload is perfomed by passing a Content-Type: multipart/form-data request header. All the param values should be passed as MultipartFormDataPartType.\n\n#### Example\n\n```swift\ndo {\n    try await Request(method: .post,\n\t    url: \"https://mywebsite.com/upload\",\n\t    params: [\n\t\t\"file1\": MultipartFormDataPartType.url(.init(filePath: \"/path/to/file.zip\")),\n\t\t\"file2\": MultipartFormDataPartType.data(data: Data(), filename: \"test.png\", contentType: \"zip\"),\n\t\t\"expiration_date\": MultipartFormDataPartType.value(value: Date.now.ISO8601Format())\n\t    ])\n    .asyncUpload(as: ResponseModel.self) { _, _, progress in\n\tdebugPrint(\"\\(progress * 100)% completed\")\n    }\n\n    debugPrint(\"Upload finished)\n} catch let error {\n    debugPrint(error)\n}\n```\n\n\u003ca name=\"file_download\"\u003e\u003c/a\u003e\n\n## File Download\n\nYou can use the **.download**, **.asyncDownload** methods of a Request object to start a download operation. The only thing you need to pass is the local file path of the file to be saved.\n\n#### Example\n\n```swift\n\nguard var localFile = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first.appendPathComponent(\"download.zip\") else {\n    return\n}\n\ndo {\n    try await Request(method: .get, \n\t\t      url: \"https://mywebsite.com/files/download.zip\")\n\t.asyncDownload(destinationPath: localFile.path,\n\t\t       progressUpdate: { bytesSent, totalBytes, progress in\n\t\tdebugPrint(\"\\(progress * 100)% completed\")\n\t})\n} catch let error {\n    debugPrint(error.localizedDescription)\n}\n\ndebugPrint(\"File saved to: \\(localFile.path)\")\n\n```\n\n\u003ca name=\"error_handling\"\u003e\u003c/a\u003e\n\n## Error Handling\n\nTermiNetwork provides its own error types (TNError) for all the possible error cases. These errors are typically returned in onFailure callbacks of **start** methods.\n\nTo see all the available errors, please visit the \u003ca href=\"https://billp.github.io/TermiNetwork/Enums/TNError.html\" target=\"_blank\"\u003eTNError\u003c/a\u003e in documentation.\n\n\n#### Example\n\n```swift\nClient\u003cTodosRepository\u003e().request(for: .add(title: \"Go shopping!\"))\n      .success(responseType: Todo.self) { todo in\n         // do something with todo\n      }\n      .failure: { error in\n          switch error {\n          case .notSuccess(let statusCode):\n               debugPrint(\"Status code \" + String(statusCode))\n               break\n          case .networkError(let error):\n               debugPrint(\"Network error: \" + error.localizedDescription)\n               break\n          case .cancelled:\n               debugPrint(\"Request cancelled\")\n               break\n          default:\n               debugPrint(\"Error: \" + error.localizedDescription)\n       }\n```\n\nor with **async await**\n```swift\ndo {\n    let todo: Todo = Client\u003cTodosRepository\u003e()\n\t.request(for: .add(title: \"Go shopping!\"))\n\t.async()\n} catch let error {\n    switch error as? TNError {\n    case .notSuccess(let statusCode, let data):\n         let errorModel = try? data.deserializeJSONData() as MyErrorModel\n\t debugPrint(\"Status code \" + String(statusCode) + \". API Error: \" + errorModel?.errorMessage)\n\t break\n    case .networkError(let error):\n\t debugPrint(\"Network error: \" + error.localizedDescription)\n\t break\n    case .cancelled:\n\t debugPrint(\"Request cancelled\")\n\t break\n    default:\n\t debugPrint(\"Error: \" + error.localizedDescription)\n }\n```\n\u003ca name=\"request_cancelling\"\u003e\u003c/a\u003e\n## Cancelling a request\nYou can cancel a request that is executing by calling the .cancel() method.\n#### Example\n\t\n```swift\nlet params = [\"title\": \"Go shopping.\"]\nlet headers = [\"x-auth\": \"abcdef1234\"]\n\nlet request = Request(method: .get, \n\t      url: \"https://myweb.com/api/todos\", \n\t      headers: headers, \n\t      params: params)\n\n\t\nrequest.success(responseType: Todo.self) { todos in\n    print(todos)\n}\n.failure { error in\n    print(error.localizedDescription)\n}\n\t\nrequest.cancel()\n```\n\nor with **async await**:\n\n```swift\n\nlet task = Task {\n    let request = Request(method: .get, \n\turl: \"https://myweb.com/api/todos\", \n\theaders: headers, \n\tparams: params)\n    do {\n        let todos: [Todo] = try await request.async()\n        print(todos)\n    } catch let error { \n        print(error.localizedDescription)\n    }\n}\n\ntask.cancel()\n```\n\n\u003ca name=\"reachability\"\u003e\u003c/a\u003e\n## Reachability\nWith Reachability you can monitor the network state of the device, like whether it is connected through wifi or cellular network.\n#### Example\n\n```swift\nlet reachability = Reachability()\ntry? reachability.monitorState { state in\n    switch state {\n    case .wifi:\n        // Connected through wifi\n    case .cellular:\n        // Connected through cellular network\n    case .unavailable:\n        // No connection\n    }\n}\n```\n\n\u003ca name=\"transformers\"\u003e\u003c/a\u003e\n## Transformers\n\nTransformers enables you to convert your Rest models to Domain models by defining your custom **transform** functions. To do so, you have to create a class that inherits the **Transformer** class and specializing it by providing the FromType and ToType generics.\n\nThe following example transforms an array of **RSCity** (rest) to an array of **City** (domain) by overriding the transform function.\n\n#### Example\n\n```swift\nfinal class CitiesTransformer: Transformer\u003c[RSCity], [City]\u003e {\n    override func transform(_ object: [RSCity]) throws -\u003e [City] {\n        object.map { rsCity in\n            City(id: UUID(),\n                 cityID: rsCity.id,\n                 name: rsCity.name,\n                 description: rsCity.description,\n                 countryName: rsCity.countryName,\n                 thumb: rsCity.thumb,\n                 image: rsCity.image)\n        }\n    }\n}\n```\n\nFinally, pass the **CitiesTransformer** in the Request's start method:\n#### Example\n```swift\nClient\u003cCitiesRepository\u003e()\n    .request(for: .cities)\n    .success(transformer: CitiesTransformer.self) { cities in\n        self.cities = cities\n    }\n    .failure { error in\n        switch error {\n        case .cancelled:\n            break\n        default:\n            self.errorMessage = error.localizedDescription\n        }\n    }\n```\n\nor with **async await**\n```swift\ndo {\n    let cities = await Client\u003cCitiesRepository\u003e()\n        .request(for: .cities)\n        .async(using: CitiesTransformer.self)\n} catch let error {\n    switch error as? TNError {\n    case .cancelled:\n        break\n    default:\n        self.errorMessage = error.localizedDescription\n    }\n}\n```\n\n\u003ca name=\"mock_responses\"\u003e\u003c/a\u003e\n## Mock responses\n**Mock responses** is a powerful feature of TermiNetwork that enables you to provide a local resource file as Request's response. This is useful, for example, when the API service is not yet available and you need to implement the app's functionality without losing any time. (Prerequisite for this is to have an API contract)\n\n#### Steps to enable mock responses\n\n1. Create a Bundle resource and put your files there. (File \u003e New -\u003e File... \u003e Settings Bundle)\n2. Specify the Bundle path in Configuration\n\t#### Example\n\t```swift\n\tlet configuration = Configuration()\n\tif let path = Bundle.main.path(forResource: \"MockData\", ofType: \"bundle\") {\n\t    configuration.mockDataBundle = Bundle(path: path)\n\t}\n\t```\n3. Enable Mock responses in Configuration\n\t#### Example\n \t```swift\n\tconfiguration.mockDataEnabled = true\n\t```\n4. Define the mockFilePath path in your endpoints.\n\t #### Example\n \t```swift\n\tenum CitiesRepository: EndpointProtocol {\n        case cities\n\n        func configure() -\u003e EndpointConfiguration {\n        switch self {\n        case .cities:\n            return EndpointConfiguration(method: .get,\n                                         path: .path([\"cities\"]),\n                                         mockFilePath: .path([\"Cities\", \"cities.json\"]))\n\t        }\n\t    }\n\t}\n\t```\n\tThe example above loads the **Cities/cities.json** from **MockData.bundle** and returns its data as Request's response.\n\nFor a complete example, open the demo application and take a look at **City Explorer - Offline Mode**.\n\n\u003ca name=\"interceptors\"\u003e\u003c/a\u003e\n## Interceptors\nInterceptors offers you a way to change or augment the usual processing cycle of a Request.  For instance, you can refresh an expired access token (unauthorized status code 401) and then retry the original request. To do so, you just have to implement the **InterceptorProtocol**.\n\nThe following Interceptor implementation tries to refresh the access token with a retry limit (5).\n\n#### Example\n```swift\nfinal class UnauthorizedInterceptor: InterceptorProtocol {\n    let retryDelay: TimeInterval = 0.1\n    let retryLimit = 5\n\n    func requestFinished(responseData data: Data?,\n                         error: TNError?,\n                         request: Request,\n                         proceed: @escaping (InterceptionAction) -\u003e Void) {\n        switch error {\n        case .notSuccess(let statusCode):\n            if statusCode == 401, request.retryCount \u003c retryLimit {\n                // Login and get a new token.\n                Request(method: .post,\n                        url: \"https://www.myserviceapi.com/login\",\n                        params: [\"username\": \"johndoe\",\n                                 \"password\": \"p@44w0rd\"])\n                    .success(responseType: LoginResponse.self) { response in\n                        let authorizationValue = String(format: \"Bearer %@\", response.token)\n\n                        // Update the global header in configuration which is inherited by all requests.\n                        Environment.current.configuration?.headers[\"Authorization\"] = authorizationValue\n\n                        // Update current request's header.\n                        request.headers[\"Authorization\"] = authorizationValue\n\n                        // Finally retry the original request.\n                        proceed(.retry(delay: retryDelay))\n                    }\n            } else {\n\t \t// Continue if the retry limit is reached\n\t    \tproceed(.continue)\n            }\n        default:\n            proceed(.continue)\n        }\n    }\n}\n\n```\n\nFinally, you have to pass the **UnauthorizedInterceptor** to the interceptors property in Configuration:\n\n#### Example\n```swift\nlet configuration = Configuration()\nconfiguration.interceptors = [UnauthorizedInterceptor.self]\n```\n\n\u003ca name=\"image_helpers\"\u003e\u003c/a\u003e\n\n## SwiftUI/UIKit Image Helpers\nTermiNetwork provides two different helpers for setting remote images.\n\u003ca name=\"swift_ui_image_helper\"\u003e\u003c/a\u003e\n### SwiftUI Image Helper\n#### Examples\n1.  **Example with URL**\n\n\t```swift\n\tvar body: some View {\n\t    TermiNetwork.Image(withURL: \"https://example.com/path/to/image.png\",\n\t\t               defaultImage: UIImage(named: \"DefaultThumbImage\"))\n\t}\n\t```\n2.  **Example with Request**\n\n\t```swift\n\tvar body: some View {\n\t    TermiNetwork.Image(withRequest: Client\u003cCitiesRepository\u003e().request(for: .image(city: city)),\n\t                       defaultImage: UIImage(named: \"DefaultThumbImage\"))\n\t}\n\t```\n\n\u003ca name=\"image_extensions\"\u003e\u003c/a\u003e\n### UIImageView, NSImageView, WKInterfaceImage Extensions\n\n1. **Example with URL**\n\n\t```swift\n\tlet imageView = UIImageView() // or NSImageView (macOS), or WKInterfaceImage (watchOS)\n\timageView.tn_setRemoteImage(url: sampleImageURL,\n\t                            defaultImage: UIImage(named: \"DefaultThumbImage\"),\n\t                            preprocessImage: { image in\n\t    // Optionally pre-process image and return the new image.\n\t    return image\n\t}, onFinish: { image, error in\n\t    // Optionally handle response\n\t})\n\t```\n2. **Example with Request**\n\n\t```swift\n\tlet imageView = UIImageView() // or NSImageView (macOS), or WKInterfaceImage (watchOS)\n\timageView.tn_setRemoteImage(request: Client\u003cCitiesRepository\u003e().request(for: .thumb(withID: \"3125\")),\n\t                            defaultImage: UIImage(named: \"DefaultThumbImage\"),\n\t                            preprocessImage: { image in\n\t    // Optionally pre-process image and return the new image.\n\t    return image\n\t}, onFinish: { image, error in\n\t    // Optionally handle response\n\t})\n\t```\n\n\u003ca name=\"middleware\"\u003e\u003c/a\u003e\n\n## Middleware\nMiddleware enables you to modify headers, params and response before they reach the success/failure callbacks. You can create your own middleware by implementing the **RequestMiddlewareProtocol** and passing it to a **Configuration** object.\n\nTake a look at *./Examples/Communication/Middleware/CryptoMiddleware.swift*  for an example that adds an additional encryption layer to the application.\n\n\u003ca name=\"debug_logging\"\u003e\u003c/a\u003e\n\n## Debug Logging\n\nYou can enable the debug logging by setting the **verbose** property to **true** in your **Configuration**.\n```swift\nlet configuration = Configuration()\nconfiguration.verbose = true\n```\n... and you will see a beautiful pretty-printed debug output in debug window\n\n\u003cimg width=\"750px\" src=\"https://user-images.githubusercontent.com/1566052/102597522-75be5200-4123-11eb-9e6e-5740e42a20a5.png\"\u003e\n\n## Tests\n\nTo run the tests open the Xcode Project \u003e TermiNetwork scheme, select Product -\u003e Test or simply press ⌘U on keyboard.\n\n## Contributors\n\nAlex Athanasiadis, alexanderathan@gmail.com\n\n## License\n\nTermiNetwork is available under the MIT license. See the LICENSE file for more info.\n","funding_links":[],"categories":["Networking","Libs","Swift","Network [🔝](#readme)"],"sub_categories":["Video","Network","Other free courses"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbillp%2FTermiNetwork","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbillp%2FTermiNetwork","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbillp%2FTermiNetwork/lists"}