{"id":22050165,"url":"https://github.com/icapps/ios-cara","last_synced_at":"2026-03-03T04:40:16.220Z","repository":{"id":33645495,"uuid":"159954452","full_name":"icapps/ios-cara","owner":"icapps","description":"Our generic webservice layer","archived":false,"fork":false,"pushed_at":"2024-05-16T22:00:31.000Z","size":31747,"stargazers_count":1,"open_issues_count":7,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-08T23:33:57.379Z","etag":null,"topics":["async-await","cara","carthage","certificate-pinning","cocoapods","concurrency","ios","macos","networking","public-key-pinning","request","response","serializer","swift","swift-package-manager","tvos","webservice-layer","xcode"],"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/icapps.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2018-12-01T14:48:54.000Z","updated_at":"2022-04-05T20:55:42.000Z","dependencies_parsed_at":"2025-05-08T23:39:17.105Z","dependency_job_id":null,"html_url":"https://github.com/icapps/ios-cara","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/icapps/ios-cara","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fios-cara","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fios-cara/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fios-cara/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fios-cara/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icapps","download_url":"https://codeload.github.com/icapps/ios-cara/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fios-cara/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30032063,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T03:27:35.548Z","status":"ssl_error","status_checked_at":"2026-03-03T03:27:09.213Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["async-await","cara","carthage","certificate-pinning","cocoapods","concurrency","ios","macos","networking","public-key-pinning","request","response","serializer","swift","swift-package-manager","tvos","webservice-layer","xcode"],"created_at":"2024-11-30T14:18:04.730Z","updated_at":"2026-03-03T04:40:16.204Z","avatar_url":"https://github.com/icapps.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](./Images/CaraShield.jpg)\n\n[![CI Status](https://travis-ci.org/icapps/ios-cara.svg?branch=master)\n[![Language Swift 4.0](https://img.shields.io/badge/Language-Swift%204.2-orange.svg?style=flat)](https://swift.org)\n\n\u003e Cara is the webservice layer that is (or should be) most commonly used throughout our apps.\n\n## TOC\n\n- [Installation](#installation)\n    - [Swift Package Manager](#swift-package-manager)\n    - [Cocoapods](#cocoapods)\n- [Features](#features)\n    - [Configuration](#configuration)\n    - [Interceptor](#interceptor)\n    - [Trigger a Request](#trigger-a-request)\n    - [Serialization](#serialization)\n        - [Custom Serializer](#custom-serializer)\n        - [Codable Serializer](#codable-serializer)\n    - [Logger](#logger)\n        - [Custom Logger](#custom-logger)\n        - [Console Logger](#console-logger)\n    - [Public Key Pinning](#public-key-pinning)\n- [Contribute](#contribute)\n  - [How to contribute?](#how-to-contribute-)\n  - [Contributors](#contributors)\n- [License](#license)\n\n## Installation 💾\n\n## Swift Package Manager\n\nYou can install `Cara` using the Swift Package Manager. This is available starting from Xcode 11. Just search for `icapps/ios-cara` and install it.\n\n## Cocoapods\n\nCara is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your `Podfile`:\n\n```ruby\npod 'Cara', git: 'https://github.com/icapps/ios-cara.git', commit: '...'\n```\n\n_Pass the correct commit reference to make sure your code doesn't break in future updates._\n\n## Features\n\n### Configuration\n\nIn order to use the service layer you have to configure it. This can be done by implementing the `Configuration` protocol and passing it to the `Service` init function.\n\n```swift\nlet configuration: Configuration = SomeConfiguration()\nlet service = Service(configuration: configuration)\n```\n\nOnce this is done you are good to go. For more information on what configuration options are available, take a look at the documentation inside the `Configuration.swift` file.\n\n### Interceptor\n\nAn intercept will _intercept_ the request when an error of type `ResponseError` occurs.\n\nWhen this happens `intercept(_:data:retry:)` will be triggered and you should return `true` or `false` to indicate if you want the normal response flow to stop. When you stop the flow it will be possible to retry the request by calling the `retry()` block.\n\n```swift\nfunc intercept(_ error: ResponseError, data: Data?, retry: @escaping () -\u003e Void) -\u003e Bool {\n    if error == .unauthorized {\n        // Execute the retry block after one second. The failed request will be retried.\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: retry)\n        return true\n    } else if error == .serviceUnavailable {\n        // When this error occurs the flow will be stopped. So the normal completion block after serialization will not be \n        // triggered.\n        return true\n    } else {\n        // Continue the flow and start serializing.\n        return false\n    }\n}\n```\n\n⚠️ The `Request` can define it it can be intercepted with the `isInterceptable` property. This should always be true in most cases. But, for example, the refresh tokens request should not be intercepted.\n\n### Trigger a Request\n\nIn order to trigger a request you have to do 2 things:\n- Create a request that conforms to `Request`.\n    \n    _The request configuration will be done in this instance. For more information on what options are available, take a look at the documentation inside the `Request.swift` file._\n\n- Create a serializer  that conforms to `Serializer`.\n\n    _The serialization of the response will be done here. You have to implement the `serialize(data:error:response:)` function and this will be called when the response completes._\n\nOnce both instances are created and you `Service` is configured, you can execute the request.\n\n```swift\nlet request: Request = SomeRequest()\nlet serializer: Serializer = JSONSerializer()\nlet task = service.execute(request, with: serializer) { response in\n    ...\n}\n```\n\nThe `response` returned by the completion block is the same as result of the serializer's `serialize(data:error:response:)` function. Executing a request returns a `URLSessionDataTask`, this can be used to, for example, cancel the request.\n\nWhen you trigger a request on a custom `queue`, the execution block will return on that same `queue`. This way you have full control of the threading in your application.\n\n### Serialization\n\nWith every request execution you have to pass a serializer. In most cases you will be able to use our `CodableSerializer`, but when you want to define a custom way of serializing your data, there is room for that too.\n\n#### Custom Serializer\n\nCreate a custom class that conforms to `Serializer`. Here is a small example of how to do this.\n\n```swift\nstruct CustomSerializer: Serializer {\n    enum Response {\n        case .success\n        case .failure(Error)\n    }\n\n    func serialize(data: Data?, error: Error?, response: HTTPURLResponse?) -\u003e Response {\n        // data: data returned from the service request\n        // error: error returned from the service request\n        // response: the service request response\n\n        if let error = error {\n            return .failure(error)\n        } else {\n            return .success\n        }\n    }\n}\n```\n\n#### Codable Serializer\n\nWe aleady supplied our **Cara** framework with one serializer: the `CodableSerializer`.\n\nThis serializer can parse the json data returned from the service to your codable models. Here is an example of a simple `Codable` model:\n\n```swift\nclass User: Codable {\n    let name: String\n}\n```\n\nLet's now see how we can serialize the result of a request to a single `User` model:\n\n```swift\nlet request = SomeRequest()\nlet serializer = CodableSerializer\u003cUser\u003e()\nservice.execute(request, with: serializer) { response in\n    switch response {\n    case .success(let model):\n        // The `model` instance is the parsed user model.\n    case .failure(let error):\n        // The `error` instance is the error returned from the service request.\n    }\n    ...\n}\n```\n\nBut what if multiple models are returned? Easy:\n\n```swift\nlet request = SomeRequest()\nlet serializer = CodableSerializer\u003c[User]\u003e()\nservice.execute(request, with: serializer) { response in\n    switch response {\n    case .success(let models):\n        // The `models` array contains the parsed `User` models.\n    case .failure(let error):\n        // The `error` instance is the error returned from the service request.\n    }\n    ...\n}\n```\n\nWhen required you can pass a custom `JSONDecoder` through the `init`.\n\n### Logger\n\nYou can get some information about the request and it's response. This can come in handy when you want to log all the request to the Console. In order to get what you want to have to create an object that conforms to `Logger` and pass it to `Cara` through the `Configuration`.\n\n#### Custom Logger\n\nCreate a custom class that conforms to `Logger`. Here is a small example of how to do this.\n\n```swift\nstruct CustomLogger: Logger {\n    func start(urlRequest: URLRequest) {\n        // Triggered just before a request if fired\n    }\n\n    func end(urlRequest: URLRequest, urlResponse: URLResponse, metrics: URLSessionTaskMetrics, error: Error?) {\n        // Triggered just after the request finised collecting the metrics\n    }\n}\n```\n\nWhen you want to use the `CustomLogger` in your application you have to pass it to the `loggers` array in the `configuration`.\n\n```swift\nclass SomeConfiguration: Configuration {\n    ...\n\n    var loggers: [Logger]? {\n        return [CustomLogger()]\n    }\n}\n```\n\n#### Console Logger\n\nWe aleady supplied our **Cara** framework with one logger: the `ConsoleLogger`.\n\nThis logger send the request and response information through `os_log` to the console. Below is an example of the printed logs:\n\n```swift\n```\n\nWhen you want to use the `ConsoleLogger` in your application you have to pass it to the `loggers` array in the `configuration`.\n\n```swift\nclass SomeConfiguration: Configuration {\n    ...\n\n    var loggers: [Logger]? {\n        return [ConsoleLogger()]\n    }\n}\n```\n\n### Public Key Pinning\n\nYou can also make sure that some URL's are pinned for security reasons. It's fairly simple on how you can do this. Just add the correct host with it's SHA256 encryped public key to the `publicKeys` property of the `Configuration`.\n\n```swift\nclass SomeConfiguration: Configuration {\n    ...\n\n    var publicKeys: PublicKeys? {\n        return [\n            \"apple.com\": \"9GzkflclMUOxhMgy32AWL/OGkMZF/5NIjvL8M/4rb3k=\",\n            \"google.com\": \"l2Z/zhy2hByKIqvgRkpKRm6M234/2HAEwiPXx5T8YYI=\"\n        ]\n    }\n}\n```\n\n\u003e There is a quick way to get the correct public key for a certain domain. Go to [SSL Server Test](https://www.ssllabs.com/ssltest/) by SSL Labs in order to perform an analysis of the SSL configuration of any web server. In the inputfield you enter the domain in order to get the process started. On the next page click the first IP address that appears, and on the page after, you'll notice the `Pin SHA256` field. The value is the public key string we need.\n\n## Contribute\n\n### How to contribute ❓\n\n1. Add a Github issue describing the missing functionality or bug.\n2. Implement the changes according to the `Swiftlint` coding guidelines.\n3. Make sure your changes don't break the current version. (`deprecate` is needed)\n4. Fully test the added changes.\n5. Send a pull-request.\n\n### Contributors 🤙\n\n- Jelle Vandebeeck, [@fousa](https://github.com/fousa)\n\n## License\n\nCara is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficapps%2Fios-cara","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficapps%2Fios-cara","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficapps%2Fios-cara/lists"}