{"id":25613645,"url":"https://github.com/sajjon/cachingservice","last_synced_at":"2025-10-29T15:43:57.570Z","repository":{"id":87412521,"uuid":"110124935","full_name":"Sajjon/CachingService","owner":"Sajjon","description":null,"archived":false,"fork":false,"pushed_at":"2018-10-24T11:52:30.000Z","size":41022,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-22T01:36:18.918Z","etag":null,"topics":["api-wrapper","caching","networking","services","swift"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sajjon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-11-09T14:23:46.000Z","updated_at":"2019-06-12T09:20:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"929fa53e-93f7-45bb-99f1-6bd9a171335a","html_url":"https://github.com/Sajjon/CachingService","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Sajjon/CachingService","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FCachingService","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FCachingService/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FCachingService/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FCachingService/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sajjon","download_url":"https://codeload.github.com/Sajjon/CachingService/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sajjon%2FCachingService/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281650531,"owners_count":26537957,"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-10-29T02:00:06.901Z","response_time":59,"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":["api-wrapper","caching","networking","services","swift"],"created_at":"2025-02-22T01:36:23.674Z","updated_at":"2025-10-29T15:43:57.563Z","avatar_url":"https://github.com/Sajjon.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Easily write API services, cache models, and per request specify options\n\n# What is this?\nThis is a protocol-driven RxSwift based convenience project for quick creation of services. You get everything for free conforming to this simple protocol:\n\n\n```swift\nprotocol Service {\n    var httpClient: HTTPClientProtocol { get }\n}\n```\n\n# Usage\n\n```swift\nprotocol CoinService: Service, Persisting {\n    func getCoins(fromSource source: ServiceSource) -\u003e Observable\u003c[Coin]\u003e\n}\n\nfinal class DefaultCoinService: CoinService {\n    let httpClient: HTTPClientProtocol\n    let cache: AsyncCache\n}\n\nextension DefaultCoinService {\n   func getCoins() -\u003e Observable\u003c[Coin]\u003e {\n        return get(request: CoinRouter.all, from: .cacheAndBackend)\n    }\n}\n```\n\nThe [CoinService.swift](Example/Source/Services/CoinService.swift) above conforms to `Service` which is all you need to get access to the methods `get`, `post` etc of a service. Have a look at the [`Example`](Example) app, fetching a list of cryptocurrencies which you can filter.\n\n# Implementation\n\n## Service\n\nActually, the protocol `Service` looks like this, but since all functions have default implementations, so the only thing you need to in order to conform to `Service` is to have `httpClient` property.\n\n```swift\nprotocol Service {\n    var httpClient: HTTPClientProtocol { get }\n\n    // Methods that have default implementations, which you should not implement your self.\n    func get\u003cModel\u003e(request: Router, from source: ServiceSource, jsonDecoder: JSONDecoder, key: Key?) -\u003e Observable\u003cModel\u003e where Model: Codable\n    \n    func post\u003cModel\u003e(request: Router, jsonDecoder: JSONDecoder) -\u003e Observable\u003cModel\u003e where Model: Codable\n    func postFireForget(request: Router) -\u003e Observable\u003cVoid\u003e\n    \n    // These should preferrably be `private`, however \"overridden\" by ImageService\n    func getFromBackend\u003cModel\u003e(request: Router, from source: ServiceSource, jsonDecoder: JSONDecoder) -\u003e Observable\u003cModel?\u003e where Model: Codable\n    func getFromCacheIfAbleTo\u003cModel\u003e(from source: ServiceSource, key: Key?) -\u003e Observable\u003cModel?\u003e where Model: Codable\n}\n```\n\n`CachingService` comes with a default HTTPClient, which you can use in your types, so you don't have to do anything really.\n\n### ServiceSource - Options\n Where you can specify `ServiceSource`, which is an `enum` with three cases:\n```swift\npublic enum ServiceSource {\n    case cacheAndBackendOptions(ServiceOptionsInfo)\n    case cache\n    case backendOptions(ServiceOptionsInfo)\n}\n```\n\nwhere `ServiceOptionsInfo` being a typealias for `[ServiceOptionsInfoItem]`:\n\n```swift\npublic enum ServiceOptionsInfoItem {\n    case emitValue\n    case emitError\n    case shouldCache\n    case ifCachedPreventDownload\n    case retryWhenReachable(ServiceRetry)\n}\n```\n\nUsing this you can easily specify if you request should fetch data from the:\n1.  Cache only\n2.  Backend only\n3.  Cache and backend\n    - Only fetch from backend if the cache was empty\n    - Skip updating cache if value existed and got new from backend\n\nFor all requests towards your backend you can specify the retry policy for failing request:\n```swift\npublic enum ServiceRetry {\n    case count(Int) // TODO: Should be able to specify paus period between retries\n    case forever // TODO: Should be using exponential backoff\n    case timeout(TimeInterval)\n}\n```\n\n## RxSwift - Nothing but Observables\n`CachingService` relies heavily on RxSwift.\n\n\nThe heart of the project lies in the method `getFromBackendAndCacheIfAbleTo` below:\n\n```swift\n    func getFromBackendAndCacheIfAbleTo\u003cModel\u003e(request: Router, from source: ServiceSource, jsonDecoder: JSONDecoder, key: Key?) -\u003e Observable\u003cModel\u003e where Model: Codable {\n        return getFromBackend(request: request, from: source, jsonDecoder: jsonDecoder)\n            .retryOnConnect(options: source.retryWhenReachable, reachability: reachability)\n            .catchError { self.handleErrorIfNeeded($0, from: source) }\n            .flatMap { model in self.updateCacheIfAbleTo(with: model, from: source, key: key) }\n            .filterNil()\n            .filter(source.emitEventForValueFromBackend)\n    }\n```\n\n\n\n## Persisting\nOptionally you can mark your service with the conformance to the `Persisting` protocol:\n\n```swift\nprotocol Persisting {\n    var cache: AsyncCache { get }\n\n    // Has default implementation\n    func getModels\u003cModel\u003e(using filter: FilterConvertible) -\u003e Observable\u003c[Model]\u003e where Model: Codable \u0026 Filterable\n}\n```\n\nOnce again, the `getModels` already has a default implementation, so really you just need the `cache: AsyncCache` property.\n\n### AsyncCache\n\nThe `AsyncCache` protocol looks like this:\n```swift\nprotocol AsyncCache: Cache {\n    func asyncSave\u003cValue\u003e(value: Value, for key: Key, done: Done\u003cVoid\u003e?) where Value: Codable\n    func asyncDelete(for key: Key, done: Done\u003cVoid\u003e?)\n    func asyncDeleteAll(done: Done\u003cVoid\u003e?)\n    \n    func asyncLoadValue\u003cValue\u003e(for key: Key, done: Done\u003cValue?\u003e?) where Value: Codable\n    func asyncHasValue\u003cValue\u003e(ofType type: Value.Type, for key: Key, done: Done\u003cBool\u003e?) where Value: Codable\n}\n```\n\n`CachingService` comes with a default implementation of said protocol, conformed to by the type `Storage` of the project [`Cache`](https://github.com/hyperoslo/Cache)(Pod/Carthage support exists).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fcachingservice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsajjon%2Fcachingservice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsajjon%2Fcachingservice/lists"}