{"id":15038825,"url":"https://github.com/provir/webserviceswift","last_synced_at":"2026-04-07T13:31:50.311Z","repository":{"id":56927401,"uuid":"101470694","full_name":"ProVir/WebServiceSwift","owner":"ProVir","description":"Network layer as Service. ","archived":false,"fork":false,"pushed_at":"2020-04-19T14:50:36.000Z","size":554,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-13T15:16:48.277Z","etag":null,"topics":["alamofire","network","network-layer","service","swift","swift4"],"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/ProVir.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}},"created_at":"2017-08-26T07:33:07.000Z","updated_at":"2022-03-06T13:35:40.000Z","dependencies_parsed_at":"2022-08-21T05:50:44.196Z","dependency_job_id":null,"html_url":"https://github.com/ProVir/WebServiceSwift","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FWebServiceSwift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FWebServiceSwift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FWebServiceSwift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ProVir%2FWebServiceSwift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ProVir","download_url":"https://codeload.github.com/ProVir/WebServiceSwift/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243428437,"owners_count":20289317,"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":["alamofire","network","network-layer","service","swift","swift4"],"created_at":"2024-09-24T20:40:22.388Z","updated_at":"2025-12-29T13:27:41.686Z","avatar_url":"https://github.com/ProVir.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":" ![WebServiceSwift](https://raw.githubusercontent.com/ProVir/WebServiceSwift/master/WebServiceSwiftLogo.png) \n\n\n[![CocoaPods Compatible](https://cocoapod-badges.herokuapp.com/v/WebServiceSwift/badge.png)](http://cocoapods.org/pods/WebServiceSwift)\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/ProVir/WebServiceSwift)\n[![Platform](https://cocoapod-badges.herokuapp.com/p/WebServiceSwift/badge.png)](http://cocoapods.org/pods/WebServiceSwift)\n[![License](https://cocoapod-badges.herokuapp.com/l/WebServiceSwift/badge.png)](https://github.com/ProVir/WebServiceSwift/blob/master/LICENSE)\n\nNetwork layer as Service. Service as an interface for interacting with your web server. Support Swift 5. \n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Communication](#communication)\n- [Installation](#installation)\n- [Usage (English / Русский)](#usage-english--%D0%A0%D1%83%D1%81%D1%81%D0%BA%D0%B8%D0%B9)\n- [Endpoints](#endpoints)\n- [Manage requests](#manage-requests)\n- [Providers](#providers)\n- [Storages](#storages)\n- [Mock Endpoints](#mock-endpoints)\n- [Author](#author)\n- [License](#license)\n\n\n## General scheme use WebServiceSwift in project.\n\n ![Scheme](https://raw.githubusercontent.com/ProVir/WebServiceSwift/master/WebServiceScheme_v3.png) \n\n\n## Features\n\n- [x] Easy interface for use\n- [x] All work with network in inner endpoint and storage, hided from interface. \n- [x] Support Dispatch Queue.\n- [x] One class for work with many types requests. \n- [x] One instance for work with many endpoints and storages.\n- [x] Simple storages (on disk, data base or in memory) in package. Easy - add only own api endpoint and ready!\n- [x] Support NetworkActivityIndicator on iOS.\n- [x] Thread safe.\n- [x] Responses with concrete type in completion handler closures. \n- [x] RequestProvider for work with only concrete request. Used to indicate more explicit dependencies (DIP). \n- [x] Mock endpoints for temporary or test response data without use real api endpoint. \n- [x] Full support Alamofire (include base endpoint).\n- [x] Simple HTTP Endpoints (NSURLSession or Alamofire).  \n- [x] Support as static framework\n\n\n## Requirements\n\n- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+\n- Xcode 9.0 and above\n- Swift 4.0 and above\n\n\n## Communication\n\n- If you **need help**, go to [provir.ru](http://provir.ru)\n- If you **found a bug**, open an issue.\n- If you **have a feature request**, open an issue.\n- If you **want to contribute**, submit a pull request.\n\n\n\n## Installation\n\n### CocoaPods\n\n[CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:\n\n```bash\n$ gem install cocoapods\n```\n\n\u003e CocoaPods 1.1.0+ is required to build WebServiceSwift 3.1.0+.\n\nTo integrate WebServiceSwift into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\nsource 'https://github.com/CocoaPods/Specs.git'\nplatform :ios, '8.0'\n\ntarget '\u003cYour Target Name\u003e' do\n    pod 'WebServiceSwift', '~\u003e 3.1'\nend\n```\n\nAlso you can use Alamofire endpoints:\n```ruby\npod 'WebServiceSwift/Alamofire', '~\u003e 3.1'\n```\n\nOr only core without simple endpoints and storages:\n```ruby\npod 'WebServiceSwift/Core', '~\u003e 3.1'\n```\n\n\nThen, run the following command:\n\n```bash\n$ pod install\n```\n\n### Carthage\n\n[Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.\n\nYou can install Carthage with [Homebrew](http://brew.sh/) using the following command:\n\n```bash\n$ brew update\n$ brew install carthage\n```\n\nTo integrate WebServiceSwift into your Xcode project using Carthage, specify it in your `Cartfile`:\n\n```ogdl\ngithub \"ProVir/WebServiceSwift\" ~\u003e 3.1\n```\n\nRun `carthage update` to build the framework and drag the built `WebServiceSwift.framework` into your Xcode project.\n\n### Swift Package Manager\n\nThe [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but WebServiceSwift does support its use on supported platforms. \n\nOnce you have your Swift package set up, adding WebServiceSwift as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/ProVir/WebServiceSwift.git\", from: \"3.1.0\")\n]\n```\n\n### Manually\n\nIf you prefer not to use any of the aforementioned dependency managers, you can integrate WebServiceSwift into your project manually.\n\nCopy files from directory `Source` in your project. \n\n\n---\n\n## Usage (English / Русский)\n\nTo use the library, you need:\n1. Create at least one type of request that implements the `WebServiceRequesting` protocol.\n2. Create at least one class for work with the network (endpoint), implementing the protocol `WebServiceEndpoint`. It should provide its own protocol, the implementation of which with queries using the extensions will allow this endpoint to process the request (see example).\n3. If desired, you can create a class for storing caches last responses or use the existing - `WebServiceFileStorage`, `WebServiceDataBaseStorage` or `WebServiceMemoryStorage`.\n4. If desired, you can create a class for mocks requests when part API don't completed - `WebServiceMockEndpoint` or its subclass, `WebServiceMockRequestEndpoint` for one type request. It is recommended to use it first in the array of `endpoints`.\n5. Write method to generate a `WebService` object. For example can be used factory to generate a WebService object or write an extension for it with a convenience constructor. \n\n**Note:** To use the library, remember to include it in each file: `import WebServiceSwift`.\n\nThe project has a less abstract example of using the library, which can be downloaded separately. Study the classes `WebServiceSimpleEndpoint` и `WebServiceAlamofireSimpleEndpoint` - they are a good example of its endpoint.\n\nTo create non-compliant copies of a service with the same set of endpoints and storages, you can call `WebService.clone()`. Each copy independently manages its requests and cancels them automatically when an instance of the service is deinited.\n\n#\n\nДля использования библиотеки вам нужно:\n1. Создать как минимум один тип запроса, реализующий протокол `WebServiceRequesting`. \n2. Создать как минимум один класс для работы с сетью, реализующий протокол `WebServiceEndpoint`.  Он должен предоставлять собственный протокол, реализация которого у запросов с помощью расширения позволит этому классу обрабатывать запрос (смотрите пример).\n3. По желанию можно создать класс для хранения кешей последних ответов или использовать существующие - `WebServiceFileStorage`, `WebServiceDataBaseStorage` или `WebServiceMemoryStorage`.\n4. По желанию можно создать класс для обработки mock запросов в случаях, когда часть АПИ не реализована - `WebServiceMockEndpoint` или наследоваться от него, `WebServiceMockRequestEndpoint` для одного типа запроса. Рекомендуется использовать его первым в списке `endpoints`. \n5. Написать метод получение готового объекта сервиса `WebService` . К примеру, можно использовать фабрику или написать для сервиса расширение с конструктором, вызывающий базовый конструктор с параметрами.\n\n**Замечание:** для использования библиотеки не забудьте ее подключить в каждом файле: `import WebServiceSwift`.\n\nВ проекте есть менее абстрактный пример использования библиотеки, который можно скачать отдельно.  Изучите классы `WebServiceSimpleEndpoint` и `WebServiceAlamofireSimpleEndpoint` - они являются хорошим примером своего обработчика (endpoint).\n\nДля создания независмых копий сервиса с одинаковым набором обработчиков и хранилищ вы можете вызвать `WebService.clone()`. Каждая копия независимо управляет своими запросами и отменяет их автоматически при удалении экземпляра сервиса. \n\n\n### Endpoints\n\n#### An example request structure (swift 4.1+):\n\n```swift\nstruct ExampleRequest: WebServiceRequesting, Hashable {\n    let param1: String  \n    let param2: Int \n    \n    typealias ResultType = String\n}\n```\n\nSuch types of requests can be as many as you like and for their processing you can use different endpoints, automatically selected from the array. There can also be several versions of the storage.\nTypically, you should create your own endpoint to interact with the API, but sometimes you may have enough of the basic functionality of the ready-made solutions - `WebServiceSimpleEndpoint` or` WebServiceAlamofireSimpleEndpoint`.\n\nТаких типов запросов может быть сколько угодно и для их обработки можно использовать разные обработчики (endpoints), выбираемые из списка автоматически. Разновидностей хранилищ тоже может быть несколько.\nКак правило вам следует создать свой собственный обработчик (endpoint) для взаимодействия с АПИ, но иногда может хватить базового функционала готовых решений - `WebServiceSimpleEndpoint` или `WebServiceAlamofireSimpleEndpoint`. \n\n\n#### Example of a own endpoint for working with a network using a library [Alamofire](https://github.com/Alamofire/Alamofire):\n\n```swift\nprotocol WebServiceHtmlRequesting: WebServiceBaseRequesting {\n    var url: URL { get }\n}\n\nclass WebServiceHtmlEndpoint: WebServiceEndpoint {\n    let queueForRequest: DispatchQueue? = DispatchQueue.global(qos: .background)\n    let queueForDataProcessing: DispatchQueue? = nil\n    let queueForDataProcessingFromStorage: DispatchQueue? = DispatchQueue.global(qos: .background)\n    let useNetworkActivityIndicator = true\n\n    func isSupportedRequest(_ request: WebServiceBaseRequesting, rawDataTypeForRestoreFromStorage: Any.Type?) -\u003e Bool {\n        return request is WebServiceHtmlRequesting\n    }\n\n    func performRequest(requestId: UInt64, request: WebServiceBaseRequesting,\n                        completionWithRawData: @escaping (_ data: Any) -\u003e Void,\n                        completionWithError: @escaping (_ error: Error) -\u003e Void) {\n\n        guard let url = (request as? WebServiceHtmlRequesting)?.url else {\n            completionWithError(WebServiceRequestError.notSupportRequest)\n            return\n        }\n\n        Alamofire.request(url).responseData { response in\n            switch response.result {\n            case .success(let data):\n                completionWithRawData(data)\n\n            case .failure(let error):\n                completionWithError(error)\n            }\n        }\n    }\n\n    func canceledRequest(requestId: UInt64) { /* Don't support in example */ }\n\n    func dataProcessing(request: WebServiceBaseRequesting, rawData: Any, fromStorage: Bool) throws -\u003e Any {\n        guard request is WebServiceHtmlRequesting, let binary = rawData as? Data else {\n            throw WebServiceRequestError.notSupportDataProcessing\n        }\n    \n        if let result = String(data: binary, encoding: .utf8) ?? String(data: binary, encoding: .windowsCP1251) {\n            return result\n        } else {\n            throw WebServiceResponseError.invalidData\n        }\n    }\n}\n```\n\n#### Example of a endpoint with use Alamofire base endpoint:\n\n```swift\nclass WebServiceHtmlV2Endpoint: WebServiceAlamofireBaseEndpoint {\n    init() {\n        super.init(queueForRequest: DispatchQueue.global(qos: .background), useNetworkActivityIndicator: true)\n    }\n\n    override func isSupportedRequest(_ request: WebServiceBaseRequesting, rawDataTypeForRestoreFromStorage: Any.Type?) -\u003e Bool {\n        return request is WebServiceHtmlRequesting\n    }\n\n    override func performRequest(requestId: UInt64, data: RequestData) throws -\u003e Alamofire.DataRequest? {\n        guard let url = (data.request as? WebServiceHtmlRequesting)?.url else {\n            throw WebServiceRequestError.notSupportRequest\n        }\n\n        return Alamofire.request(url)\n    }\n\n    override func dataProcessing(request: WebServiceBaseRequesting, rawData: Any, fromStorage: Bool) throws -\u003e Any {\n        guard request is WebServiceHtmlRequesting, let binary = rawData as? Data else {\n            throw WebServiceRequestError.notSupportDataProcessing\n        }\n\n        if let result = String(data: binary, encoding: .utf8) ?? String(data: binary, encoding: .windowsCP1251) {\n            return result\n        } else {\n            throw WebServiceResponseError.invalidData\n        }\n    }\n}\n```\n\nImportant - the data passed to `completionWithRawData` can not always be of the `Data` type, in this case it is recommended that this type implement the `WebServiceRawDataSource` protocol. Binary data is needed to be able to save them as Raw in the storage. It is worth noting that from theStorage to the handler, the read data comes in the form of `Data` and it should be taken into account.\n\nВажный момент - данные передаваемые в `completionWithRawData` не всегда могут иметь тип `Data`, в этом случае рекомендуется чтобы этот тип реализовывал протокол `WebServiceRawDataSource`. Бинарные данные нужны для возможности сохранять их как Raw в кеше. Стоит обратить внимание на то, что из хранилища в обработчик прочитанные данные поступают как `Data` и следует это предусмотреть.\n\n#### Example:\n\n```swift\n/// Data from server as raw, used only as example\nstruct ServerData: WebServiceRawDataSource {\n    let statusCode: Int\n    let binary: Data\n\n    var binaryRawData: Data? { return binary }\n}\n\nfunc dataProcessing(request: WebServiceBaseRequesting, rawData: Any, fromStorage: Bool) throws -\u003e Any {\n    guard request is WebServiceHtmlRequesting else {\n        throw WebServiceRequestError.notSupportDataProcessing\n    }\n\n    let binary: Data\n    if let data = rawData as? Data {\n        //Data from Storage\n        binary = data\n    } else if let data = rawData as? ServerData {\n        //Data from server\n        binary = data.binary\n    } else {\n        throw WebServiceRequestError.notSupportDataProcessing\n    }\n\n    return String(data: binary, encoding: .utf8) ?? String(data: binary, encoding: .windowsCP1251) ?? \"\"\n}\n```\n\nWorking with the network is not a prerequisite for the endpoint. Behind this layer, you can hide the work with the database or at least temporarily put a stub. On the interface service side, this is not important and during the development of the endpoint can be unnoticeably replaced, without changing the code of the requests themselves.\n\nВовсе не обязательно обработчик (endpoint) должен работать с сетью. За этим слоем вы можете скрыть работу с БД или вовсе временно выставить заглушку. Со стороны интерфейса сервиса это не важно и в процессе разработки обработчики можно незаметно подменять, не меняя код самих запросов. \n\n\n#### Simple request support concrete endpoint example:\n\n```swift\nextension ExampleRequest: WebServiceHtmlRequesting {\n    var url: URL {\n        /* .... Logic create URL from param1 and param2 ... */\n    }\n    \n    func decodeResponse(data: Data) throws -\u003e String {\n        /* ... Create concrete decoder data from server and decoding ... */\n    }\n}\n```\n\nEach request must implement the support protocols for each endpoint that can handle it. If multiple endpoints are supported by a single request, then the first endpoint, supported from the array `endpoints`, is selected for processing.\n\nКаждый запрос должен реализовать протоколы поддержки каждого обработчика (endpoint), который может его обрабатывать. Если поддерживается несколько обработчиков одним запросом, то выбирается первый поддерживаемый из списка `endpoints` для обработки. \n\n\n#### WebService create service - used factory constructor example:\n\n```swift\nextension WebService {\n    convenience init() {\n        let endpoint = WebServiceHtmlV2Endpoint()\n        \n        var storages: [WebServiceStoraging] = []\n        if let storage = WebServiceDataBaseStorage() {\n            storages.append(storage)\n        }\n        \n        self.init(engines: [WebServiceMockEndpoint(), endpoint], storages: storages)\n    }\n}\n```\n\nYou can also make support for a singleton - an example of this approach is in the source code example.\n\nТакже можно сделать поддержку синглетона - пример такого подхода есть в исходниках примера. \n\n\n#### An example of using the closure:\n\n```swift\nlet webService = WebService()\n\nwebService.performRequest(ExampleRequest(param1: val1, param2: val2)) { [weak self] response in\n    switch response {\n    case .canceledRequest: \n        break\n\n    case .data(let dataCustomType):\n        self?.dataFromServer = dataCustomType\n        \n    case .error(let error):\n        self?.showError(error)\n    }\n}\n```\n\nWe pass the data in request, on the output we get the ready object for display.\n\nПередаем данные в запросе, на выходе получаем готовый объект для отображения. \n\n\n#### An example using a delegate:\n\n```swift\nlet webService = WebService()\n\nwebService.performRequest(ExampleRequest(param1: val1, param2: val2), responseDelegate: self)\n\nfunc webServiceResponse(request: WebServiceRequesting, key: AnyHashable?, isStorageRequest: Bool, response: WebServiceAnyResponse) {\n    if let request = request as? ExampleRequest {\n        let response = response.convert(request: request)\n\n        switch response {\n        case .canceledRequest: \n            break\n\n        case .data(let dataCustomType):\n            if isStorageRequest {\n                dataFromStorage = dataCustomType\n            } else {\n                dataFromServer = dataCustomType\n            }\n                \n        case .error(let error):\n            showError(error)\n        }\n    }\n}\n```\n\n### Manage requests\n\nExecutable requests can be controlled - check for execution (containt) and cancel. In endpoint, you can implement a method of canceling query execution - this is necessary for optimizations, regardless of whether you cancel the request in the endpoint, the request will be canceled and its results ignored.\n\nYou can manage requests in several ways:\n- All: `containsManyRequests()` and `cancelAllRequests()`;\n- On a request instance, if it is hashabled (`Request: WebServiceBaseRequesting, Hashable`): `containsRequest(Request)` and `cancelRequests(Request)`;\n- By request type: `containsRequest(type: WebServiceBaseRequesting.Type)` and `cancelRequests(type: WebServiceBaseRequesting.Type)`;\n- By the key (more on this): `containsRequest(key:)` and `cancelRequests(key:)`;\n- By type of key: `containsRequest(keyType:)` and `cancelRequests(keyType:)`.\n\nAll canceled requests will end with an response `WebServiceResponse.canceledRequest(duplicate: false)`.\n\n#\n\nВыполняемыми запросами можно управлять - проверять на выполнение (containt) и отменять (cancel). В endpoint можно реализовать метод отмены выполнения запроса - это нужно для оптимизаций, не зависимо отмените ли вы запрос в endpoint, запрос будет отменен и его результаты проигнорированы. \n\nЗапросами можно управлять несколькими способами:\n- Всеми: `containsManyRequests()` и `cancelAllRequests()`;\n- По экземпляру запроса, если он хешируемый (`Request: WebServiceBaseRequesting, Hashable`): `containsRequest(Request)` и `cancelRequests(Request)`;\n- По типу запроса: `containsRequest(type: WebServiceBaseRequesting.Type)` и `cancelRequests(type: WebServiceBaseRequesting.Type)`;\n- По ключу (об этом далее): `containsRequest(key:)` и `cancelRequests(key:)`;\n- По типу ключа: `containsRequest(keyType:)` и `cancelRequests(keyType:)`.\n\nВсе отмененные запросы завершатся с ответом `WebServiceResponse.canceledRequest(duplicate: false)`.\n\n\n#### Example contains and cancel requests:\n\n```swift\nstruct ExampleKey: Hashable {\n    let value: String\n}\n\nlet isContains1 = webService.containsRequest(ExampleRequest(param1: val1, param2: val2))\nlet isContains2 = webService.containsRequest(type: ExampleRequest.self)\nlet isContains3 = webService.containsRequest(key: ExampleKey(value: val1))\nlet isContains3 = webService.containsRequest(keyType: ExampleKey.self)\n\nwebService.cancelRequests(ExampleRequest(param1: val1, param2: val2))\nwebService.cancelRequests(type: ExampleRequest.self)\nwebService.cancelRequests(key: ExampleKey(value: val1))\nwebService.cancelRequests(keyType: ExampleKey.self)\n```\n\nYou can also exclude duplicate requests. For this, the request must implement the protocol `Hashable` or use a key when perform request (any type that implements the protocol` Hashable`).\nRequests that turn out to be duplicates will immediately end with the response `WebServiceResponse.canceledRequest(duplicate: true)`.\n\nТакже можно исключать дублирующие запросы. Для этого запрос должен реализовывать протокол `Hashable` или использовать ключ (key) при запросе (любой тип реализующий протокол `Hashable`). \nЗапросы которые окажутся дублирующими сразу завершатся с ответом `WebServiceResponse.canceledRequest(duplicate: true)`.\n\n#### Example use test for duplicates requests:\n\n```swift\nwebService.performRequest(ExampleRequest(param1: val1, param2: val2), excludeDuplicate: true) { [weak self] response in\n    switch response {\n    case .canceledRequest(duplicate: let duplicate): \n        if duplicate {\n            print(\"Request is duplicate!\")\n        }\n\n    case .data(let dataCustomType):\n        self?.dataFromServer = dataCustomType\n\n    case .error(let error):\n        self?.showError(error)\n    }\n}\n\nwebService.performRequest(ExampleRequest(param1: val1, param2: val2), key: ExampleKey(value: val1), excludeDuplicate: true) { [weak self] response in\n    switch response {\n    case .canceledRequest(duplicate: let duplicate): \n        if duplicate {\n            print(\"Key is duplicate!\")\n        }\n\n    case .data(let dataCustomType):\n        self?.dataFromServer = dataCustomType\n\n    case .error(let error):\n        self?.showError(error)\n    }\n}\n```\n\n### Providers\n\nTo add more explicit dependencies to your project, as well as to protect against certain errors, you can use providers. Providers are wrappers over WebService and hide it with private access. They provide access only to a limited type of requests in the form of convenient interfaces, excluding a certain class of errors in the code. The main purpose of the provider is to give the access to the permissible part of the WebService functional in the right place in the code.\n\nДля добавления более явных зависимостей в ваш проект, а также для защиты от некоторых ошибок, вы можете использовать провайдеры. Провайдеры являются обертками над WebService и скрывают его private доступом. Они предоставляют доступ только ограниченному типу запросов в виде удобных интерфейсов, исключающие определенный класс ошибок в коде. Основная цель провайдера - в нужном месте в коде дать доступ только к допустимой части функционала WebService. \n\n#### Example own provider:\n\n```swift\nenum SiteWebServiceRequests {\n    struct Example: WebServiceRequesting, Hashable {\n        let site: String  \n        let domainRu: Bool\n    \n        typealias ResultType = String\n    }\n    \n    struct GetList: WebServiceEmptyRequesting, Hashable {\n        typealias ResultType = [String]\n    }\n}\n\n\nclass SiteWebProvider: WebServiceProvider {\n    private let webService: WebService\n\n    required init(webService: WebService) {\n        self.webService = webService\n    }\n    \n    enum Site: String {\n    case google\n    case yandex\n    }\n\n    func requestExampleData(site: Site, domainRu: Bool = true, completionHandler: @escaping (_ response: WebServiceResponse\u003cString\u003e) -\u003e Void) {\n        webService.performRequest(SiteWebServiceRequests.Example(site: site.rawValue, domainRu: domainRu), completionHandler: completionHandler)\n    }\n}\n```\n\nYou can use ready-made generic provider class - `WebServiceRequestProvider` for one type of request.\n\nВы можете использовать готовый шаблоны класса провайдера - `WebServiceRequestProvider` для одного типа запроса. \n\n#### Example providers:\n\n```swift\nlet getListSiteWebProvider: WebServiceRequestProvider\u003cSiteWebServiceRequests.GetList\u003e\nlet exampleSiteWebProvider: WebServiceRequestProvider\u003cSiteWebServiceRequests.Example\u003e\n\ninit(webService: WebService) {\n    getListSiteWebProvider = webService.createProvider()\n    exampleSiteWebProvider = webService.createProvider()\n}\n\nfunc performRequests() {\n    // RequestProvider for WebServiceEmptyRequesting\n    getListSiteWebProvider.performRequest() { [weak self] response in\n        switch response {\n        case .canceledRequest(duplicate: let duplicate): \n            break\n    \n        case .data(let list):\n            self?.sites = list\n    \n        case .error(let error):\n            self?.showError(error)\n        }\n    }\n    \n    // RequestProvider for request with params\n    exampleSiteWebProvider.performRequest(.init(site: \"google\", domainRu: false)) { _ in }\n}\n```\n\n\n### Storages\n\nIn order for the application to work without the Internet, it is very useful to save the received data in permanent storage, because data stored on the user's device is read as a rule much faster than through the network and does not depend on the state of connection to the Internet.  In most cases, this is enough to save the last received response from the server and provide it on demand. It is for this case that the storage in the service is provided.\n\nYou can create your own storage class by implementing the `WebServiceStorage` protocol. But in most cases this is not required, since there are already ready classes for use that cover all the necessary cases of use - `WebServiceFileStorage`, `WebServiceDataBaseStorage` and `WebServiceMemoryStorage`. In most cases, you will only use one to choose from, but in the case of more complex logic, you can combine and repeat them with different settings, separating them by the classification of the data (more on this below).\n\nNot every request is stored in the storage, but only those that meet either the general storage protocol (recommended) or the protocol of a particular storage type.\nData can be stored as a rule in two versions:\n- Raw: raw data from the server (usually binary). This type of data after reading is sent for processing to a suitable endpoint;\n- Value: processed data. This type of data is immediately sent as a result after reading.\n\nRaw is convenient because you do not need to write the converter to binaries, since the incoming data from the server is usually already in this form.\nValue is more optimized, since the data is already processed and does not require re-processing, but it is required to provide converters to binary type and vice versa (Codable is usually used).\nIf the complexity of processing from the server and in the value converter is the same, then it is much better to use Raw, otherwise, if possible Value - depends on the data and processing request.\n\nUsually storages can provide along with the data (timeStamp) when the data has been saved - this allows you to evaluate whether the data is outdated for use.\n\nThe data in storages can be deleted according to one of the following characteristics:\n- For concrete request: `WebService.deleteInStorage(request:)`;\n- All data in certain storages, intended only for a specific classification data: `WebService.deleteAllInStorages(withDataClassification:)`;\n- All data in certain storages intended for any classification, storages with a specific list of data classifications will be omitted: `WebService.deleteAllInStoragesWithAnyDataClassification()`;\n- All data in all storages: `WebService.deleteAllInStorages()`;\n\n#\n\nДля того чтобы приложение могло работать без интернета, очень полезно сохранять полученные данные в постоянном хранилище, т.к. данные сохраненные на устройстве пользователя читаются как правило куда быстрее чем через сеть и не зависят от состояния подключения к сети интернет.  В большинстве случаев для этого хватает сохранять последний полученный ответ с сервера и предоставлять его по требованию. Именно для этого случая предосмотрено хранилище в сервисе.\n\nВы можете сделать свой класс хранилища - нужно реализовать протокол `WebServiceStorage`. Но как правило этого не требуется, т.к. уже есть готовые классы для использования, которые покрывают все необходимые случаи использования - `WebServiceFileStorage`, `WebServiceDataBaseStorage` и `WebServiceMemoryStorage`. В большинстве случаев вы будете использовать только один на выбор, но в случае более сложной логики их можно комбинировать и повторять с разными настройками, разделяя их классификацией данных (подробнее об этом ниже). \n\nНе каждый запрос сохраняется в хранилище, а только те которые соотвествуют либо общему протоколу хранения (рекомендуется), либо протоколу конкретного типа храннилища. \nДанные могут храниться как правило в двух вариантах:\n- Raw: необработанные данные с сервера (как правило бинарные). Такой тип данных после чтения отправляется на обработку в подходящий обработчик (endpoint);\n- Value: обработанные данные. Такой тип данных после чтения сразу отправляется как результирующий. \n\nRaw удобен тем что не нужно писать конвертер в бинаные данные, т.к. приходящие данные с сервера как правило уже в этом виде.\nValue более оптимизирован, т.к. данные уже обработаны и не требует повтороной обработки, но требуется предоставить конвертеры в бинарный тип и обратно (обычно используется Codable). \nЕсли сложность обработки с сервера и в конветере в бинарный тип одинакова, то приемущественно лучше использовать Raw, иначе по возможности Value - зависит от данных и обработчика запроса. \n\nОбычно хранилища могут предоставить вместе с данными время (timeStamp) когда данные были сохранены - это позволяет оценить устарели ли данные для использования. \n\nДанные в хранилищах можно удалять по одному из признаков:\n- Для конкретного запроса: `WebService.deleteInStorage(request:)`;\n- Все данные в определенных хранилищах, предназначенные только для данных определенной классификации: `WebService.deleteAllInStorages(withDataClassification:)`;\n- Все данные в определенных хранилищах, предназначенные для любой классификации, хранилища с конкретным списком классификаций будут пропущены: `WebService.deleteAllInStoragesWithAnyDataClassification()`;\n- Все данные во всех хранилищах: `WebService.deleteAllInStorages()`;\n\n\n#### Example support request storing:\n\n```swift\nextension SiteWebServiceRequests.Example: WebServiceRequestRawGeneralStoring {\n    var identificatorForStorage: String? {\n        return \"SiteWebServiceRequests_Example\"\n    }\n}\n\nextension SiteWebServiceRequests.GetList: WebServiceRequestValueGeneralStoring {\n    var identificatorForStorage: String? {\n        return \"SiteWebServiceRequests_GetList\"\n    }\n    \n    func writeDataToStorage(value: [String]) -\u003e Data? {\n        return try? PropertyListEncoder().encode(value)\n    }\n    \n    func readDataFromStorage(data: Data) throws -\u003e [String]? {\n        return try PropertyListDecoder().decode([String].self, from: data)\n    }\n}\n```\n\nTo separate the storage method, requests can be classified by a specific type of storage - which you decide. By default, all data is classified as `WebServiceDefaultDataClassification = \"default\"`. \n\nFor example, this case can be popular: ordinary caches, user caches (deleted when leaving the account) and temporary caches stored only in RAM while the application is running. Each data class has its own storage.\n\nДля удобства разделения способа хранения, запросы можно классификовать по определенному типу хранения - какому именно решаете вы. По умолчанию все данные классифицируются как `WebServiceDefaultDataClassification = \"default\"`. \n\nК примеру, может быть популярен такой случай: обычные кеши, кеши пользователя (удаляются при выходе из аккаунта) и временные кеши хранящийся только в оперативной памяти пока приложение запущено. На каждый класс данных есть свое хранилище. \n\n#### Example data classification:\n\n```swift\nenum WebServiceDataClass: Hashable {\n    case user\n    case temporary\n}\n\nextension WebService {\n    static func create() -\u003e WebService {\n        let endpoint = WebServiceHtmlV2Endpoint()\n\n        var storages: [WebServiceStorage] = []\n        \n        // Support temporary in memory Data Classification\n        storages.append(WebServiceMemoryStorage(supportDataClassification: [WebServiceDataClass.temporary]))\n   \n        // Support user Data Classification\n        if let dbUserURL = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(\"userCaches.sqlite\"),\n           let storage = WebServiceDataBaseStorage(sqliteFileUrl: dbUserURL, supportDataClassification: [WebServiceDataClass.user]) {\n            storages.append(storage)\n        }\n        \n        // Support any Data Classification (also can use WebServiceDataBaseStorage())\n        if let storage = WebServiceFileStorage() {\n            storages.append(storage)\n        }\n\n        return .init(endpoints: [endpoint], storages: storages)\n    }\n    \n    func clearUserCaches() {\n        deleteAllInStorages(withDataClassification: WebServiceDataClass.user)\n    }\n}\n\nextension UserWebServiceRequests.GetInformation: WebServiceRequestRawGeneralStoring {\n    var dataClassificationForStorage: AnyHashable { \n        return WebServiceDataClass.user\n    }\n\n    var identificatorForStorage: String? {\n        return \"UserInformation\"\n    }\n}\n```\n\nData from storage should always be requested explicitly. This request can be linked to the request to the server in two versions:\n- `WebService.ReadStorageDependencyType.dependSuccessResult`: The request to storage will be canceled if the data from the server comes before without an error;\n- `WebService.ReadStorageDependencyType.dependFull`: The request to storage will be canceled if the data from the server comes earlier without error or the request to the server itself will be canceled or it will be a duplicate.\n\nThe request to the server to which the request to storage is attached should be called immediately after the request to storage - this request will be bound regardless of its type. \nIt is possible to cancel explicit requests in storage only through the main request to the server associated with it as `.dependFull`.\n\n\nДанные из хранилища всегда нужно запрашивать явно. Это запрос можно привязать к запросу на сервер в двух вариантах:\n- `WebService.ReadStorageDependencyType.dependSuccessResult`: Запрос к хранилищу будет отменен если данные с сервера придут раньше без ошибки;\n- `WebService.ReadStorageDependencyType.dependFull`: Запрос к хранилищу будет отменен если данные с сервера придут раньше без ошибки или сам запрос на сервер будет отменен или окажется дублирующим.\n\nЗапрос к серверу, к которому привязывается запрос к хранилищу должен быть вызван сразу после запроса к хранилищу - именно этот запрос будет привязан в независимости от его типа. \nОтменять явно запросы в хранилище можно только через связанный как `.dependFull` основной запрос на сервер. \n\n\n#### Example read data in storages:\n\n```swift\nlet request = ExampleRequest(param1: val1, param2: val2)\n\nwebService.readStorageData(request, dependencyNextRequest: .dependFull) { [weak self] timeStamp, response in\n    if case .data(let data) = response {\n        if let timeStamp = timeStamp, timeStamp.timeIntervalSinceNow \u003e -3600 {   //no longer than 1 hour\n            self?.dataFromStorage = data\n        }\n    }\n}\n\nwebService.performRequest(request, excludeDuplicate: true) { [weak self] response in\n    switch response {\n    case .canceledRequest: \n        break\n\n    case .data(let dataCustomType):\n        self?.dataFromServer = dataCustomType\n\n    case .error(let error):\n        self?.showError(error)\n    }\n}\n\nwebService.readStorageData(TestRequest(), dependencyNextRequest: .notDepend) { [weak self] timeStamp, response in\n    if case .data(let data) = response {\n        self?.testData = data\n    }\n}\n\n/// responseOnlyData - ignore errors and canceled read\nwebService.readStorage(ExampleRequest(param1: val1, param2: val2), dependencyNextRequest: .dependSuccessResult, responseOnlyData: true, responseDelegate: self)\n```\n\n\n### Mock Endpoints\n\nIf part of the API is not available or you just need to generate temporary test data, you can use the `WebServiceMockEndpoint`. The mock endpoint emulates receiving and processing data from a real server and returns exactly the data that you specify. To maintain its request, you need to extend the request to the protocol `WebServiceMockRequesting`.\n\nЕсли часть API недоступна или вам просто нужно предоставить временные тестовые данные, вы можете использовать `WebServiceMockEndpoint`. Mock endpoint эмулирует получение и обработку данных с реального сервера и возвращает те данные, которые вы указали. Для поддержки его запросом, нужно запрос расширить до протокола `WebServiceMockRequesting`. \n\n#### Example of request support mock engine:\n\n```swift\nextension ExampleRequest: WebServiceMockRequesting {\n    var isSupportedRequestForMock: Bool { return true }\n    var mockTimeDelay: TimeInterval? { return 3 }\n\n    var mockHelperIdentifier: String? { return \"template_html\" }\n    func mockCreateHelper() -\u003e Any? {\n        return \"\u003chtml\u003e\u003cbody\u003e%[BODY]%\u003c/body\u003e\u003c/html\u003e\"\n    }\n\n    func mockResponseHandler(helper: Any?) throws -\u003e String {\n        if let template = helper as? String {\n            return template.replacingOccurrences(of: \"%[BODY]%\", with: \"\u003cb\u003eHello world!\u003c/b\u003e\")\n        } else {\n            throw WebServiceResponseError.invalidData\n        }\n    }\n}\n```\n\nTo support your request types instead of `WebServiceMockRequesting`, you can create your mock class by inheriting from `WebServiceMockEndpoint` and overriding the functions `isSupportedRequest()` and `convertToMockRequest`. The latter function converts your request into a suitable type with the implementation of the protocol `WebServiceMockRequesting`. Its class for processing mocks can be useful in the first place for a unit tests - so as not to add the implementation of mocks to the main code.\n\nThe other endpoint is well suited for a unit tests - `WebServiceMockRequestEndpoint`. Each instance is intended only for one type of request, all processing is indicated in the place of configuration of the service. Such endpoints can be any number.\n\n\nДля поддержки своих типов запросов взамен `WebServiceMockRequesting` вы можете создать свой mock класс наследовавшись от `WebServiceMockEndpoint` и переопределив функции `isSupportedRequest()` и `convertToMockRequest`. Последняя функция конвертирует ваш запрос в подходящий ему тип с реализацией протокола `WebServiceMockRequesting`.  Свой класс для обработки моков может быть полезен в первую очередь для юнит тестов - чтобы не добавлять реализации моков в основной код.\n\nДругой вариант, хорошо подходит для юнит тестов - `WebServiceMockRequestEndpoint`. Каждый экземпляр предназначен только для одного типа запроса, вся обработка указывается в месте настройки сервиса. Таких обработчиков может быть сколько угодно. \n\n#### Example used WebServiceMockRequestEndpoint:\n\n```swift\nlet template = \"\u003chtml\u003e\u003cbody\u003e%[BODY]%\u003c/body\u003e\u003c/html\u003e\"\nlet mockRequest = WebServiceMockRequestEndpoint.init(timeDelay: 3) { (request: ExampleRequest) -\u003e String in\n    return template.replacingOccurrences(of: \"%[BODY]%\", with: \"\u003cb\u003eHello world from MockRequestEndpoint!\u003c/b\u003e\")\n}\n\nlet webService = WebService(endpoints: [mockRequest, mockRequest2, mockRequest3], storages: [])\n```\n\n\n## Author\n\n[**ViR (Короткий Виталий)**](http://provir.ru)\n\n\n## License\n\nWebServiceSwift is released under the MIT license. [See LICENSE](https://github.com/ProVir/WebServiceSwift/blob/master/LICENSE) for details.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprovir%2Fwebserviceswift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprovir%2Fwebserviceswift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprovir%2Fwebserviceswift/lists"}