{"id":1653,"url":"https://github.com/RuiAAPeres/Reactor","last_synced_at":"2025-08-02T04:32:34.525Z","repository":{"id":62447037,"uuid":"53686945","full_name":"RuiAAPeres/Reactor","owner":"RuiAAPeres","description":"Powering your RAC architecture ","archived":false,"fork":false,"pushed_at":"2018-08-12T00:06:35.000Z","size":109094,"stargazers_count":184,"open_issues_count":1,"forks_count":11,"subscribers_count":49,"default_branch":"master","last_synced_at":"2025-07-31T09:48:56.618Z","etag":null,"topics":[],"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/RuiAAPeres.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":"2016-03-11T18:07:45.000Z","updated_at":"2023-10-30T12:58:24.000Z","dependencies_parsed_at":"2022-11-01T23:17:23.013Z","dependency_job_id":null,"html_url":"https://github.com/RuiAAPeres/Reactor","commit_stats":null,"previous_names":["mailonline/reactor"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/RuiAAPeres/Reactor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RuiAAPeres","download_url":"https://codeload.github.com/RuiAAPeres/Reactor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RuiAAPeres%2FReactor/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268334618,"owners_count":24233793,"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-08-02T02:00:12.353Z","response_time":74,"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":[],"created_at":"2024-01-05T20:15:52.387Z","updated_at":"2025-08-02T04:32:32.239Z","avatar_url":"https://github.com/RuiAAPeres.png","language":"Swift","funding_links":[],"categories":["Networking"],"sub_categories":["Video","Other free courses"],"readme":"![](Logo/logo.png)\n\n[![Build Status](https://travis-ci.org/MailOnline/Reactor.svg?branch=master)](https://travis-ci.org/MailOnline/Reactor)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![CocoaPods](https://img.shields.io/cocoapods/v/MOReactor.svg)](https://cocoapods.org/)\n[![Swift 3.0](https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat)](https://developer.apple.com/swift/)\n[![Platforms iOS](https://img.shields.io/badge/Platforms-iOS-lightgray.svg?style=flat)](https://developer.apple.com/swift/)\n\n#### Intro\n\nMost applications out there follow the same pattern:\n\n 1. Is `T` persisted and has not expired?\n  1. **Yes**: Use it ✅\n  2. **No**: Fetch `T` from the network\n    1. Do we have an internet connection?\n      1. **Yes**: make the Request.\n        1. Create `T` from the network response's data and persist it (send any error that might occur) ✅\n        2. Request failed: send an error ❌\n       2. **No**: send an error ❌\n\nIf we look carefully the only thing that changes is the `T`. Reactor provides the whole infrastructure around `T` with minimum configuration, but with flexibility in mind. In order to achieve that, it uses:\n\n* [Network](https://github.com/MailOnline/Reactor/tree/master/Reactor/Core/Network)\n* [Parser](https://github.com/MailOnline/Reactor/tree/master/Reactor/Core/Parser)\n* [Persistence](https://github.com/MailOnline/Reactor/tree/master/Reactor/Core/Persistence)\n* [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa) as its only dependency.\n\n##### Pros... ✅\n\n* One of the biggest Pros of Reactor, is how intrinsically forces you to decouple your different components. If your persistence is coupled with your network, Reactor is not for you. 🌳\n* Since Reactor provides most of the insfrastructure out of the box, you can quickly create your entire Model layer. This is useful if you are creating a prototype or a demo. 🚀\n* It removes most of the boilerplate you usually need, when creating a project that follows the flow described in the [Intro](https://github.com/MailOnline/Reactor#intro). ✨\n\n##### Cons... ❌\n\n* If you have an unusual flow, that doesn't really fit the flow described in the [Intro](https://github.com/MailOnline/Reactor#intro). ⛔️\n* After checking the [Advance usage](#advance-usage), Reactor doesn't give you enough flexibility. 😭😭 If this is the case, please open an issue, so we see what we can do! 👍\n\n## How to use\n\n#### Carthage\n\n```\ngithub \"MailOnline/Reactor\"\n```\n\n#### Cocoapods\n\n```\n# Since there is already a podfile named `Reactor`, we are using `MOReactor`.\npod 'MOReactor', '~\u003e 0.9'\n```\n\n### Basic setup\n\nFor Reactor to work, you need to make sure your Model objects comply with the `Mappable` protocol. This protocol allows you to encode and decode an object. This is necessary for parsing the object (coming from the network) and storing it on disk.\n\nLet's use the `Author` struct as an example ([this can be found in the Unit tests](https://github.com/MailOnline/Reactor/blob/master/ReactorTests/Tests/Stubs/Article.swift)). The first step is to make the `Author`\nstruct compliant with the `Mappable` protocol: \n\n```swift\nstruct Author {\n  let name: String\n}\n\nextension Author: Mappable { \n\n  static func mapToModel(object: AnyObject) -\u003e Result\u003cAuthor, MappedError\u003e {\n\n  guard\n    let dictionary = object as? [String: AnyObject],\n    let name = dictionary[\"name\"] as? String\n    else { return Result(error: MappedError.Custom(\"Invalid dictionary @ \\(Author.self)\\n \\(object)\"))}\n\n    let author = Author(name: name)\n\n    return Result(value: author)\n  }\n \n  func mapToJSON() -\u003e AnyObject {\n    return [\"name\": self.name]\n  }\n}\n```\n**Note:** The above implementation, is just an example, you are free to use whatever means you prefer.\n\nThe first function `mapToModel` is what allows a model object to be created (JSON ➡️ Model). The second function `mapToJSON` is the inverse (Model ➡️ JSON).\n\nThe second step would be:\n\n```swift\nlet baseURL = NSURL(string: \"https://myApi.com\")!\nlet configuration = FlowConfiguration(persistenceConfiguration: .Enabled(withPath: \"path_to_persistence\"))\n\nlet flow: ReactorFlow\u003cAuthor\u003e = createFlow(baseURL, configuration: configuration)\nlet reactor: Reactor\u003cAuthor\u003e = Reactor(flow: flow)\n```\n\nNow that you have the `reactor` ready, it exposes two functions:\n\n```swift\nfunc fetch(resource: Resource) -\u003e SignalProducer\u003cT, Error\u003e\nfunc fetchFromNetwork(resource: Resource) -\u003e SignalProducer\u003cT, Error\u003e\n```\n\nWe find that these are the two most common scenarios:\n\n1. When you get to a new screen, you try to get some data. In this case, Reactor checks the persistence first and if it fails it will fallback to the network.\n2. You want fresh data, so Reactor will use the network.\n\nThe final piece is the `Resource`, which is nothing more than a struct that encapsulates how the request will be made:\n\n* path\n* query\n* body\n* HTTP headers\n* HTTP method\n\n### Configuration\n\nFor extra flexibility, you can use the `CoreConfiguration` and `FlowConfiguration` protocols. \n\nThe `CoreConfiguration` protocols define how the Reactor behaves:\n\n```swift\npublic protocol CoreConfiguration {\n/// When enabled, you should pass the path where it will be stored\n/// Otherwise it's disabled\nvar persistenceConfiguration: PersistenceConfiguration { get }\n/// If the `saveToPersistenceFlow`, should be part of the flow.\n/// Should be `false` when the flow shouldn't\n/// wait for `saveToPersistenceFlow` to finish (for example it takes\n/// a long time).\n/// Note: if you set it as `false` and it fails, the failure will be\n/// lost, because it's not part of the flow, but injected instead .\n/// `true` by default.\nvar shouldWaitForSaveToPersistence: Bool { get }\n}\n```\n\nThe `FlowConfiguration` protocol define how the Reactor Flow is created:\n\n\n```swift\npublic protocol FlowConfiguration {\n/// If persistence should be used.\n/// `true` by default.\nvar usingPersistence: Bool { get }\n/// If reachability should be used.\n/// `true` by default.\nvar shouldCheckReachability: Bool { get }\n/// If the parser should be strict or prune the bad objects.\n/// Pruning will simply remove objects that are not parseable, instead\n/// of erroring the flow. Strict on the other hand as soon as it finds\n/// a bad object will error the entire flow.\n/// Note: if you receive an entire batch of bad objects, it will default to\n/// an empty array. Witch leads to not knowing if the server has no results or\n/// all objects are badly formed.\n/// `true` by default.\nvar shouldPrune: Bool { get }\n}\n```\n\nThe `FlowConfiguration` protocol is used in the following methods:\n\n```swift\npublic func createFlow\u003cT where T: Mappable\u003e(baseURL: NSURL, configuration: FlowConfigurable) -\u003e ReactorFlow\u003cT\u003e\npublic func createFlow\u003cT where T: Mappable\u003e(connection: Connection, configuration: FlowConfigurable) -\u003e ReactorFlow\u003cT\u003e\npublic func createFlow\u003cT where T: SequenceType, T.Generator.Element: Mappable\u003e(baseURL: NSURL, configuration: FlowConfigurable) -\u003e ReactorFlow\u003cT\u003e\npublic func createFlow\u003cT where T: SequenceType, T.Generator.Element: Mappable\u003e(connection: Connection, configuration: FlowConfigurable) -\u003e ReactorFlow\u003cT\u003e\n```\n\nThese are convenient methods, that provide a ready to use `ReactorFlow`. **It's important to note**, that if you would like to use a custom persistence (CoreData, Realm, SQLite, etc), you should create a `ReactorFlow` on your own. The reason why, is because the default Persistence class (`InDiskPersistence.swift`) takes a path, where the data will be saved. This might not make sense with other approaches (please check [Using 3rd Party Dependencies](https://github.com/MailOnline/Reactor#using-3rd-party-dependencies) section). \n \n\n### Without Persistence\n \nIf it doesn't make sense to persist data, you can:\n\n```swift\nlet baseURL = NSURL(string: \"https://myApi.com\")!\nlet configuration = FlowConfiguration(persistenceConfiguration: .Disabled)\nlet flow: ReactorFlow\u003cFoo\u003e = createFlow(baseURL, configuration: configuration)\nlet reactor: Reactor\u003cFoo\u003e = Reactor(flow: flow)\n```\n\nAs for the `mapToJSON` function, you can simply return an `NSNull`:\n\n```swift\nfunc mapToJSON() -\u003e AnyObject {\n  return NSNull()\n}\n```\n\n### Advance Usage\n\n#### Intro\nIn order to make most of Reactor, keep the following in mind (these are `ReactorFlow\u003cT\u003e`'s properties):\n\n```swift\nvar networkFlow: Resource -\u003e SignalProducer\u003cT, Error\u003e\nvar loadFromPersistenceFlow: Void -\u003e SignalProducer\u003cT, Error\u003e\nvar saveToPersistenceFlow: T -\u003e SignalProducer\u003cT, Error\u003e\n```\n\nAll three properties are mutable (`var`) on purpose, so you can extend specific behaviours. For example, you might be interested in knowing why `loadFromPersistenceFlow` is failing and log it. With the default flow, this is not possible to do, because if `loadFromPersistenceFlow` fails, the network flow will kick in and the error is lost. \n\nA way to accomplish this, is by creating a default flow and then extending it:\n\n```swift\nlet reactorFlow: ReactorFlow\u003cAuthor\u003e = ...\n\nlet extendedPersistence = reactorFlow.loadFromPersistenceFlow().on(failure: { error in print(error) })\nreactorFlow.loadFromPersistenceFlow =  { extendedPersistence }\n```\n\nYou can further decompose the flow, since all the core pieces are exposed in the public API. More specifically:\n\n* [`Network`](https://github.com/MailOnline/Reactor/blob/master/Reactor/Core/Network/Network.swift) and the [`Connection`](https://github.com/MailOnline/Reactor/blob/master/Reactor/Core/Network/Connection.swift) protocol\n* [`Parser`](https://github.com/MailOnline/Reactor/blob/master/Reactor/Core/Parser/Parser.swift)\n* [`InDiskPersistenceHandler\u003cT\u003e`](https://github.com/MailOnline/Reactor/blob/master/Reactor/Core/Persistence/InDiskPersistence.swift) \n\nThe default flow provided by Reactor ([Intro](https://github.com/MailOnline/Reactor#intro)) is something you are welcome to use, but not tied to. Keep in mind the following when creating your own flows:\n\nThe `Reactor\u003cT\u003e`'s `fetch` function invariant:\n\n* the `loadFromPersistenceFlow` will always be called first. If it fails, `fetchFromNetwork` is called.\n\nThe `Reactor\u003cT\u003e`'s `fetchFromNetwork` function invariant:\n\n* the `networkFlow` will always be called first, if it succeeds it will be followed by `saveToPersistenceFlow`.\n\n#### Using 3rd Party Dependencies\n\nReactor plays quite well with other dependencies and requires minimum effort from your side. In the previous section, we saw the three essencial pieces of a `ReactorFlow`:\n\n```swift\nvar networkFlow: Resource -\u003e SignalProducer\u003cT, Error\u003e\nvar loadFromPersistenceFlow: Void -\u003e SignalProducer\u003cT, Error\u003e\nvar saveToPersistenceFlow: T -\u003e SignalProducer\u003cT, Error\u003e\n```\n\nAs mentioned, we encourage you to modify them to suit your needs. With 3rd party dependencies, you have to do exactly that. As an example, these could be the steps you would go through in order to make Alamofire compatible:\n\n1. Wrap Alamofire with ReactiveCocoa. You can see an example of that [here](https://github.com/indragiek/AlamofireRACExtensions/blob/master/AlamofireRACExtensions/AlamofireRACExtensions.swift#L14#L38), [here](http://stackoverflow.com/a/34243581/491239) and [here](https://yoichitgy.github.io/post/dependency-injection-in-mvvm-architecture-with-reactivecocoa-part-3-designing-the-model/). This is a fairly trivial task and are plenty of examples out there.\n2. Make the `NSError` used by the approaches previously mentioned into an `Error`. You can use the `mapError` operator. You should then transform it into an `Error.Network`.\n3. This will now depend if you have a parser in place or not.\n 1. If you do, then you just need to hook up your previously wrapped Alamofire request with it. Ideally you will have a function with the following signature: `NSData -\u003e SignalProducer\u003cT, Error\u003e` for the parser. Composition then becomes easy: `alamofireCall().flatMap(.Latest, transformation: parse)` (a concrete example [here](https://github.com/MailOnline/Reactor/blob/master/Reactor/Reactor/ReactorFlow.swift#L67)).\n 2. If you don't, you can make use of the `Mappable` protocol and the [`parse`](https://github.com/MailOnline/Reactor/blob/master/Reactor/Core/Parser/Parser.swift#L12#L41) function provided by Reactor. Once you have that, you can follow [this](https://github.com/MailOnline/Reactor/blob/master/Reactor/Reactor/ReactorFlow.swift#L67).\n\nWith all this in place, the final piece is:\n\n```swift\nlet persistenceHandler = InDiskPersistenceHandler\u003cMyModel\u003e(persistenceFilePath: persistencePath)\nlet loadFromPersistence = persistenceHandler.load\nlet saveToPersistence =  persistenceHandler.save\n\nlet reactorFlow: ReactorFlow\u003cMyModel\u003e = ReactorFlow(network: myNetworkFlow, loadFromPersistenceFlow: loadFromPersistence, saveToPersistence: saveToPersistence)\n```\n\nThe `createFlow` family methods follow this approach internally, so you should [check them out](https://github.com/MailOnline/Reactor/blob/master/Reactor/Reactor/ReactorFlow.swift#L38#L87). \n\nOther 3rd party dependencies will follow the same approach:\n\n1. Wrap the dependency with ReactiveCocoa\n2. Make it compatible with flow signature.\n3. Create the `ReactorFlow` as it suits you. \n\n## License\nReactor is licensed under the MIT License, Version 2.0. [View the license file](LICENSE)\n\nCopyright (c) 2015 MailOnline\n\nHeader image by [Henrique Macedo](https://twitter.com/henrikemacedo). \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRuiAAPeres%2FReactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRuiAAPeres%2FReactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRuiAAPeres%2FReactor/lists"}