{"id":13781003,"url":"https://github.com/feathersjs-ecosystem/feathers-swift","last_synced_at":"2025-05-11T14:34:21.325Z","repository":{"id":19995833,"uuid":"88371160","full_name":"feathersjs-ecosystem/feathers-swift","owner":"feathersjs-ecosystem","description":"FeathersJS Swift SDK, written with love.","archived":false,"fork":false,"pushed_at":"2022-03-28T17:38:56.000Z","size":306,"stargazers_count":49,"open_issues_count":2,"forks_count":9,"subscribers_count":8,"default_branch":"develop","last_synced_at":"2025-04-20T17:17:05.444Z","etag":null,"topics":["feathers"],"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/feathersjs-ecosystem.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-04-15T20:00:16.000Z","updated_at":"2025-03-31T15:24:12.000Z","dependencies_parsed_at":"2022-08-07T09:15:48.557Z","dependency_job_id":null,"html_url":"https://github.com/feathersjs-ecosystem/feathers-swift","commit_stats":null,"previous_names":["startupthekid/feathers-ios"],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/feathersjs-ecosystem","download_url":"https://codeload.github.com/feathersjs-ecosystem/feathers-swift/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253580233,"owners_count":21930906,"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":["feathers"],"created_at":"2024-08-03T18:01:21.986Z","updated_at":"2025-05-11T14:34:21.015Z","avatar_url":"https://github.com/feathersjs-ecosystem.png","language":"Swift","funding_links":[],"categories":["iOS"],"sub_categories":["Validation"],"readme":"# FeathersSwift\n\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](#carthage) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/Feathers.svg)](#cocoapods) [![GitHub release](https://img.shields.io/github/release/feathersjs-ecosystem/feathers-swift.svg)](https://github.com/feathersjs-ecosystem/feathers-swift/releases) ![Swift 4.0.x](https://img.shields.io/badge/Swift-4.0.x-orange.svg) ![platforms](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS-lightgrey.svg) ![build](https://travis-ci.org/feathersjs-ecosystem/feathers-swift.svg?branch=master)\n\n![feathers](https://media.giphy.com/media/Fn8LZVVgTqXba/giphy.gif)\n\n## What is FeathersSwift?\n\nFeathersSwift is a Cocoa networking library for interacting with a [FeathersJS](https://feathersjs.com/) backend.\nWhy should you use it?\n\n* Swift 4 :thumbsup:\n* Network abstraction layer\n* Integrates seemlessly with any FeathersJS services\n* Supports iOS, macOS, tvOS, and watchOS\n* Reactive API ([ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift))\n\nIf you use FeathersJS (which you should), FeathersSwift is the perfect choice for you. No more dealing with HTTP requests or socket clients. One simple interface to rule them all and in the darkness, unify them :ring:.\n\n## Installation\n\n### Cocoapods\n```\npod `Feathers`\n```\n\n### Carthage\n\nAdd the following line to your Cartfile:\n\n```\ngithub \"feathersjs/feathers-swift\"\n```\n## Getting Started\n\nFeathersSwift is spread out across multiple repositories to ensure that you're only pulling in exactly what you need and no more. There are two Feathers providers, [feathers-swift-rest](https://github.com/feathersjs-ecosystem/feathers-swift-rest) and [feathers-swift-socketio](https://github.com/feathersjs-ecosystem/feathers-swift-socketio). Install either provider using the instructions on their respective READMEs.\n\nOnce you've install a provider, either rest of socketio, an instance of a `Feathers` application with it:\n\n```swift\n\nlet feathersRestApp = Feathers(RestProvider(baseURL: URL(string: \"https://myserver.com\")))\n\n```\n\nThen grab a service:\n\n```swift\nlet userService = feathersRestApp.service(\"users\")\n```\n\nFinally, make a request:\n\n```swift\nservice.request(.find(parameters: [\"name\": \"Waldo\"]))\n.on(value: { response in\n  print(response)\n})\n.start()\n```\n\nFeathersSwift's API is built entirely using [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift), an awesome functional-reactive library. Because promises aren't standard in Swift, we had to find an alternative that offers similar usage patterns. By doing this, we can avoid the pyramid of doom and endlessly nesting callbacks, instead offering a simplified reactive API.\n\n### Service\n\nThere are six types of requests you can make that correspond with Feathers service methods:\n\n```swift\npublic enum Method {\n\n    case find(query: Query?)\n    case get(id: String, query: Query?)\n    case create(data: [String: Any], query: Query?)\n    case update(id: String?, data: [String: Any], query: Query?)\n    case patch(id: String?, data: [String: Any], query: Query?)\n    case remove(id: String?, query: Query?)\n\n}\n```\n\nWith `.update`, `.patch`, and `.remove`, you may pass in nil for the id when you want to delete a list of entities. The list of entities is determined by the query you pass in.\n\nBy default, FeathersSwift will return an instance of `ProviderService` which wraps the application's transport provider in a service. However, you can also register your own services:\n\n```swift\nfeathers.use(\"users-local\", CoreDataService())\n```\n\nAll custom services must conform to `ServiceType`. Thankfully, that's easy due to the FeathersSwift provided `Service` class which handles things such as hook storage and no-op implementations of the required methods.\n\nA simple custom service might look like:\n\n```swift\nclass FileService: Service {\n\n  public override func request(_ method: Service.Method) -\u003e SignalProducer\u003cResponse, FeathersError\u003e {\n        let fileManager = FileManager.default\n        switch method {\n        case let .create(data, _):\n          guard let id = data[\"id\"] else { break }\n          let fileData = NSKeyedArchiver.archiveData(withRootObject: data)\n          fileManager.createFile(atPath: \"\\(path)/\\(id)\", contents: fileData, attributes: nil)\n        default: break\n        }\n    }\n\n}\n```\n\nWhile a tiny example, custom services can be infinitely more complex and used anywhere in the hook process. Just call `hookObject.app.service(\"my-custom-service\").request(.create(data: [:], parameters: nil))`.\n\n### Querying\n\nYou may have noticed instead of passing a dictionary of parameters through a request, FeathersSwift uses a `Query` object. The `Query` class has a simple and composable API for represent complex queries without messing around with dictionaries. It supports all the normal queries FeathersJS users have come to know and love such as `ne` or `or`, just in a simplified, type-safe manner.\n\nTo create a query:\n\n```swift\nlet query = Query()\n  .ne(\"age\", 50)\n  .limit(25)\n  .skip(5)\n\nlet service = feathers.service(\"users\")\n\nservice.request(.find(query)).start()\n\n```\n\nGone are the days of wondering if you formatted your dictionary correctly, `Query` knows how to serialize itself and takes care of that for you.\n\n### Authentication\n\nTo authenticate your application with your Feathers back end:\n\n```swift\nfeathersRestApp.authenticate([\n  \"strategy\": \"facebook-token\",\n  \"access_token\": \"ACCESS_TOKEN\"\n])\n.start()\n```\n\nAuthentication returns a JWT payload which is cached by the application and used to make subsequent requests. Currently there is not a re-authentication mechanism but look for that in coming versions.\n\nTo log out, simply call:\n\n```swift\nfeathersRestApp.logout().start()\n```\n\n### Real-Time Events\n\nWhen using the socket provider, you can not only use it to call Feathers service methods, you can also listen for real-time events. Simply use [feathers-swift-socketio](https://github.com/feathersjs/feathers-swift-socketio) create a feathers application with an instance of `SocketProvider` and register for events using `.on` on your services.\n\nThere are four different real-time events:\n\n```swift\npublic enum RealTimeEvent: String {\n\n   case created\n   case updated\n   case patched\n   case removed\n\n}\n```\n\nYou can use these events to things like dynamically update the UI, save entities to a database, or just log that the event happened.\n\n```swift\nlet feathersSocket = Feathers(provider: SocketProvider(baseURL: URL(string: \"https://myserver.com\")!, configuration: []))\n\nlet userService = feathersSocket.service(path: \"users\")\nuserService.on(.created)\n.observeValues { entity in\n  print(entity) // Prints the object that was just created\n}\n```\n\nWhen you're finished, be sure to call `.off` to unregister from the event. Otherwise your completion block will be retained by the provider.\n\n```swift\nuserService.off(.created)\n```\n\nThere's also a nifty `.once` function that does exactly what you expect; you listen for one event and one event only.\n\n### Hooks\n\n![hooks](https://media.giphy.com/media/ujGSCZeZs2yXu/giphy.gif)\n\nLike in FeathersJS, you can register `before`, `after`, and `error` hooks to run when requests are made. Possible use cases could include stubbing out a unit test with a before hook or simple logging.\n\nTo create a hook, create an object that conforms to `Hook`:\n\n```swift\npublic protocol Hook {\n    func run(with hookObject: HookObject) -\u003e Promise\u003cHookObject\u003e\n}\n```\n\nA hook that logs all `create` events might look like this:\n\n```swift\nstruct CreateLogHook: Hook {\n\n  func run(with hookObject: HookObject) -\u003e Promise\u003cHookObject\u003e {\n    var object = hookObject\n    if object.method == .create {\n      print(\"create happened\")\n    }\n    return Promise(value: object)\n  }\n\n}\n```\n\nOr you can do something more complex like a network call:\n\n```swift\nstruct FetchAssociatedUserHook: Hook {\n  func run(with hookObject: HookObject) -\u003e Promise\u003cHookObject\u003e {\n    var object = hookObject\n    guard object.app.service.path == \"groups\" else {\n      return Promise(value: hookObject)\n    }\n    guard case var .get(id, parameters)  = object.method else {\n      return Promise(value: hookObject)\n    }\n    guard let userIdentifier = parameters[\"user_id\"] as? String else {\n      return Promise(error: .myCustomError(\"no associated user found when expected to exist\"))\n    }\n    return object.app.service(\"users\").request(.get(parameters: [\"id\": userIdentifier])).then { response in\n      if case let .jsonObject(object) = response.data {\n        parameters[\"user_id\"] = object[\"id\"]\n        object.method = .get(id, parameters)\n      }\n      return Promise(value: object)\n    }\n  }\n}\n```\n\nImportant to note is `var object = hookObject`. Swift function parameters are `let` constants so you first have to copy the object if you want to modify it.\n\n#### Hook Object\n\nThe hook object gets passed around through hooks in succession. The interface matches the JS one fairly closely:\n\n```swift\n/// Hook object that gets passed through hook functions\npublic struct HookObject {\n\n    /// Represents the kind of hook.\n    ///\n    /// - before: Hook is run before the request is made.\n    /// - after: Hook is run after the request is made.\n    /// - error: Runs when there's an error.\n    public enum Kind {\n        case before, after, error\n    }\n\n    /// The kind of hook.\n    public let type: Kind\n\n    /// Feathers application, used to retrieve other services.\n    public let app: Feathers\n\n    /// The service this hook currently runs on.\n    public let service: Service\n\n    /// The service method.\n    public var method: Service.Method\n\n    /// Error that can be set which will stop the hook processing chain and run a special chain of error hooks.\n    public var error: FeathersError?\n\n    /// Result of a successful method call, only in after hooks.\n    public var result: Response?\n\n    public init(\n        type: Kind,\n        app: Feathers,\n        service: Service,\n        method: Service.Method) {\n        self.type = type\n        self.app = app\n        self.service = service\n        self.method = method\n    }\n\n}\n```\n\nAll the `var` declarations are mutable and you can set and mutate them as needed in your hooks, including `.method` if you want to do things like swap out parameters or change the method call entirely (e.g. changing a `.get` to a `.find`).\n\nImportant things to note about the hook object:\n- Setting `error` will cause the hook processing chain to stop and immediately run any error hooks. If that happens in a `before` hook, the request will also be skipped.\n- Setting `result` to some `Response` value in a `before` hook will skip the request, essentially stubbing it.\n\n#### Hook Registration\n\nTo register your hooks, you first have to create a `Service.Hooks` object:\n\n```swift\nlet beforeHooks = Service.Hooks(all: [LogResultHook()], create: [AppendUserIdHook()]])\nlet afterHooks = Service.Hooks(find: [SaveInRealmHook()])\nlet errorHooks = Service.Hooks(all: [LogErrorHook(destination: \"log.txt\")])\n```\n\nRegistering the hooks is just as easy:\n\n```swift\nlet service = app.service(\"users\")\nservice.before(beforeHooks)\nservice.after(afterHooks)\nservice.error(errorHooks)\n```\n\n**Important**: The hooks registered for `all` are run first, then the hooks for the particular service method.\n\nIf at any point you need to inspect your hooks, you can do that too using `.hooks`:\n\n```swift\n\nlet beforeHooks = service.hooks(for: .before)\n\n```\n\n### Contributing\n\nHave an issue? Open an issue! Have an idea? Open a pull request!\n\nIf you like the library, please :star: it!\n\n### Further Help\n\nFeathersSwift is extensively documented so please take a look at the source code if you have any questions about how something works.\nYou can also ping me in the [Feathers' slack team](http://slack.feathersjs.com/) @brendan.\n\nCheers! :beers:\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-swift/lists"}