{"id":15037339,"url":"https://github.com/jakehawken/concurrency","last_synced_at":"2026-01-02T01:14:43.359Z","repository":{"id":56906483,"uuid":"106152079","full_name":"jakehawken/Concurrency","owner":"jakehawken","description":"A small toolkit for handling concurrency and simplifying asynchronous work in Swift.","archived":false,"fork":false,"pushed_at":"2022-01-25T19:26:56.000Z","size":473,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-19T22:34:53.801Z","etag":null,"topics":["async","asynchronous","completion-handler","concurrency","deferred","enum","generics","promise","promises","result","result-type","rxswift","stream","swift","swift3"],"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/jakehawken.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-10-08T05:19:33.000Z","updated_at":"2023-12-31T01:52:52.000Z","dependencies_parsed_at":"2022-08-21T03:20:49.176Z","dependency_job_id":null,"html_url":"https://github.com/jakehawken/Concurrency","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FConcurrency","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FConcurrency/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FConcurrency/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakehawken%2FConcurrency/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakehawken","download_url":"https://codeload.github.com/jakehawken/Concurrency/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243402691,"owners_count":20285283,"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":["async","asynchronous","completion-handler","concurrency","deferred","enum","generics","promise","promises","result","result-type","rxswift","stream","swift","swift3"],"created_at":"2024-09-24T20:34:23.148Z","updated_at":"2026-01-02T01:14:43.299Z","avatar_url":"https://github.com/jakehawken.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Concurrency\n\n[![Version](https://img.shields.io/cocoapods/v/Concurrency.svg?style=flat)](http://cocoapods.org/pods/Concurrency)\n[![License](https://img.shields.io/cocoapods/l/Concurrency.svg?style=flat)](http://cocoapods.org/pods/Concurrency)\n[![Platform](https://img.shields.io/cocoapods/p/Concurrency.svg?style=flat)](http://cocoapods.org/pods/Concurrency)\n\nConcurrency is a small toolkit for handling concurrency and encapsulating asynchronous work in the Swift programming language. There are several different paradigms for handling concurrency, and I think they each have specific use cases. Most notably, for continuous updates, subscriptions, and streams of data, [RxSwift](https://github.com/ReactiveX/RxSwift) is pretty much king. Concurrency sets out to fill out the other use cases, and, early on, I think I make a fairly solid case why you should never use the old `(ExpectedType?, Error?)-\u003e()`-style completion handler ever again.\n\n#### Acknowledgment\nThough I did not consult their source code, my implementation of Promise/Future draws inspiration from my extensive professional and personal __*use of*__ [Deferred](https://github.com/kseebaldt/deferred) and [BrightFutures](https://github.com/Thomvis/BrightFutures).\n\n#### Table of Contents\n- [Concurrency](#concurrency)\n      - [Acknowledgment](#acknowledgment)\n      - [Table of Contents](#table-of-contents)\n  - [The First Set of Problems to Solve](#the-first-set-of-problems-to-solve)\n  - [The Cast of Characters](#the-cast-of-characters)\n    - [Result](#result)\n      - [Both:](#both)\n      - [or just one:](#or-just-one)\n      - [The problem with completion blocks: BLOCKCEPTION!](#the-problem-with-completion-blocks-blockception)\n      - [The Pyramid of Doom: It's not just for optional binding anymore!](#the-pyramid-of-doom-its-not-just-for-optional-binding-anymore)\n    - [Promise and Future](#promise-and-future)\n      - [The concept](#the-concept)\n      - [Providing Futures](#providing-futures)\n      - [Consuming futures](#consuming-futures)\n      - [Mutation](#mutation)\n        - [Map Result](#map-result)\n        - [Map Value](#map-value)\n        - [Map Error](#map-error)\n        - [Flatmap](#flatmap)\n        - [MapError\u0026lt;T, E, Q\u0026gt;](#maperrorltt-e-qgt)\n      - [Chaining](#chaining)\n        - [Then](#then)\n        - [Zip](#zip)\n        - [First Finished](#first-finished)\n      - [One last thing...](#one-last-thing)\n- [Conclusion](#conclusion)\n  - [Installation](#installation)\n  - [Author](#author)\n  - [License](#license)\n\n## The First Set of Problems to Solve\n\nLet's face it: completion handlers in Swift can be pain. Really quickly, let's break down everything obnoxious about the standard callback pattern in Swift:\n\n```Swift\nfunc getAString(_ completionHandler: (String?, Error?)-\u003e())\n```\n\nFirst off, this requires the consumer of this function to have to unrwap two different values to figure out the actual state of affairs, which ends up resulting in the user having to implement a lengthy `switch` or `if`/`else if`/`else` block to handle it.\n\n```Swift\ngetAString { (string, error) in\n  if let string = string {\n    self.doSuccessThingy(string)\n  }\n  else if let error = error {\n    self.doErrorThingy(error)\n  }\n}\n```\n\nThe user has to type this all this logic out by hand, gets no autocomplete, and no help from the compiler about missed logical cases.\n\nThis also reveals the other major problem with this pattern: Did you notice there's no `else` case? Despite covering the two logically expected result states, this is an incomplete logical statement. Horrifyingly, the nature of the callback's signature allows for the possibility of both arguments to be nil, which is a state that makes absolutely no sense to the consumer. But the fact that it's possible means that they're kind of forced to handle it.\n\nUnwrapping optionals is already something that Swift developers have to do too often anyway, and nobody likes ambiguous, potentially incoherent states of affairs, so what's the solution? Enter stage left: My good buddy Result.\n\n## The Cast of Characters\n\n### Result\n\n`Result\u003cSuccess, Failure\u003e` was added in Swift 5, and the people rejoiced. \"The people\" in this case being everyone who hates `(Success?, Error?)-\u003e(`) completion blocks, which, after reading my excellent arguments above, now includes you. In pre-2.0 versions of Concurrency, it included a Result type, but instead it now includes a bunch of extension methods/properties on Swift's native Result.\n\nAnd what do those extensions buy you? Why, *__Sweet, sweet syntactic sugar, of course!__* I've included some functional-style methods onto Result which will be very familiar to anyone who's worked with other Promise/Future libraries or with reactive frameworks like RxSwift. I've included `onSuccess(_:)` and `onError(_:)` methods which allow the consumer to implement success blocks and error blocks independently of one another. That means the consumer can implement one, the other, both, or neither. AND, since each of those methods returns a discardable reference to the Result, the consumer can easily chain them, as you can see below.\n\n#### Both:\n\n```Swift\ngetAString { (result) in\n  result.onSuccess { (value) in\n    self.doSuccessThingy(value)\n  }.onError { (error) in\n    self.doErrorThingy(error)\n  }\n}\n```\n\n#### or just one:\n\n```Swift\ngetAString { (result) in\n  result.onSuccess { (value) in\n    self.doSuccessThingy(value)\n  }\n}\n```\n\nSo, when we have to use completion blocks, these extensions on `Result` provide a nice, clean, compiler-aided, unwrap-free option.\n\n#### The problem with completion blocks: [BLOCKCEPTION!](https://www.youtube.com/watch?v=L6aT_oEhIKo)\n\nYou see, often, asynchronous work relies on other asynchronous work. In a world of traditional completion blocks, this means, at very least, writing a method that has a completion handler, which in turn calls a method which has a completion handler. And god forbid your asynchronous method need to call multiple completion blocks in succession. Then, even if you only implement the success blocks (yikes!) you'd still end up with a nightmare like this:\n\n```Swift\nfunc getAStringWithTwoNumbersInIt(_ completionHandler:(Result\u003cString, MyError\u003e)-\u003e()) {\n  getInt1 { result1 in\n    result1.onSuccess { int1 in\n      getInt2 { result2 in\n        result1.onSuccess { int2 in\n           completionHandler(.success(\"I got \\(int1) and \\(int2)! Yay!\"))\n        }\n      }\n    }\n  }\n}\n```\n\n#### The Pyramid of Doom: It's not just for optional binding anymore!\nSo, how do we solve this?\n\n### Promise and Future\n\nIf you've never been exposed to the concept of [promises and/or futures](https://en.wikipedia.org/wiki/Futures_and_promises), hoo boy am I absolutely delighted to be the one to tell you about them!\n\n#### The concept\n\nThe concept is fairly simple: A future (in some paradigms called a \"delay\" or an \"eventual\") is an object that acts as a stand-in for a future result. So if you write an asynchronous method that uses futures instead of completion blocks, the future is returned synchronously. The future represents the chunk of asynchronous work being performed by the method that returned it, and is responsible for handling its completion.\n\nSo, if you had a method that looked like this with a completion block:\n\n```Swift\nfunc askForHerPhoneNumber(_ completionHandler:(Int?, NSError?)-\u003e())\n```\n\nit would look like this, using a Future:\n\n```Swift\nfunc askForHerPhoneNumber() -\u003e Future\u003cInt, NSError\u003e\n```\n#### Providing Futures\n\nThis is where `Promise\u003cT,E\u003e` comes in. Promise pretty much has one purpose: It vends a `Future\u003cT,E\u003e`, and is responsible for resolving or rejecting it. That's it.\n\nSo, if you were writing a method that returns a promise solely for the purpose of encapsulating another method that uses a traditional completion block, it might look like this:\n\n```Swift\nfunc goCallYourMother() -\u003e Future\u003cAnEarful, CallError\u003e {\n  let promise = Promise\u003cAnEarful, CallError\u003e()\n\n  callYourMotherWithCompletion { earful, error in\n    if let earful = earful {\n      promise.resolve(earful)\n    }\n    else if let error = error {\n      promise.reject(error)\n    }\n    else {\n      let noDataError = CallError(message: \"No data returned by the call.\")\n      promise.reject(noDataError)\n    }\n  }\n\n  return promise.future\n}\n```\n\nThe key features here are:\n1) Synchronously, you create a Promise, and then you return the promise's Future.\n2) On completion of the asynchronous work, you either call the `resolve(_:)` method on the promise and pass in the success value, or the `reject(_:)` method and pass in an error of type `E`. You determine the type of error you expect when you create the Promise.\n\n#### Consuming futures\n\nThis is where it gets fun, boys and girls!\n\nFuture has two `typealias`-ed block types, `SuccessBlock` (`(T)-\u003e()`) and `ErrorBlock` (`(E)-\u003e()`), and it has two public methods which take them as arguments. These two methods are your bread and butter:\n\n`onSuccess(_:)` and `onError(_:)`\n\nCalling these give the future its completion behavior. If somehow the call comes back before either of these are set, the passed in blocks will simply be executed as soon as they're passed in. Easy-peasy.\n\nSo, consuming the future is done like so:\n\n```Swift\nlet future: Future\u003cAnEarful, PhoneCallError\u003e = goCallYourMother()\nfuture.onSuccess { earful in\n  self.hooBoy(earful)\n}.onError { error in\n  self.wellAtLeastITried(error)\n}\n```\n\nor, if you want to be even more concise, since these methods all return `@discardableResult` references, there's no need to assign the future to a variable at all:\n\n```Swift\ngoCallYourMother().onSuccess { earful in\n  self.hooBoyWhatAn(earful)\n}.onError { error in\n  self.wellAtLeastITried(error)\n}\n```\n\n__Side note:__ There's also a handy `finally(_:)` method as well, which will add a block to be executed after completion, regardless of success or failure. It executes after the given success or failure block, and is passed a `Result\u003cT,E\u003e` as its one argument.\n\nThe real magic about Future is that `onSuccess(_:)` and `onError(_:)` can be called as many times as needed, and each of the actions will execute in order. So, if you have a method which fetches a value and returns a promise, and there are multiple layers of the app that need to be updated with that value, you can pass that future along from method to method, tacking on success actions as you go.\n\nYes, I know, an example is in order. So, let's say we have that same method from earlier: `func goCallYourMother() -\u003e Future\u003cAnEarful, PhoneCallError\u003e`. We could propagate it along like so:\n\n```Swift\nfunc callYourMomAndThenReflect() -\u003e Future\u003cAnEarful, PhoneCallError\u003e {\n  return goCallYourMother().onSuccess { (earful) in\n    self.hooBoyWhatAn(earful) // a local action that needs to happen\n  }.onError { (error) in\n    self.wellAtLeastITried(error) //also a local action that needs to happen\n  }\n}\n```\n\nSo we've called a method that gets a future, tacked on a `SuccessBlock` and an `ErrorBlock` and then immediately returned that future to whoever consumed this method. The next consumer can do the same, and on and on, and as soon as the initial call completes, it will bubble up completions or errors, all the way up the chain, in order, to the last block(s) added.\n\nBut, you ask, what if the values you need at the different layers of your application are of different types?\n\n_Say no more. I got u, fam._\n\n#### Mutation\n\nFuture has a swiss army knife of mutation methods, which those familiar with Rx might find familiar. These allow for the mapping of values (`T`), errors (`E`), and combinations of the two. The first of which, does a total mapping:\n\n##### Map Result\n```Swift\n@discardableResult func mapResult\u003cNewT, NewE: Error\u003e(_ mapBlock:@escaping (Result\u003cT, E\u003e) -\u003e (Result\u003cNewT, NewE\u003e)) -\u003e Future\u003cNewT, NewE\u003e\n```\n\nThis generates a new Future with potentially different success and/or error types. When you call it, you pass in a mapping block. That block is called on completion of the first  translates from a `Result\u003cT,E\u003e` matching the `T` and `E` of your future to a `Result\u003cNewT, NewE\u003e` matching the types on your new future. This automatically generates a `Future\u003cNewT, NewE\u003e`, which completes accordingly whenever the first future completes.\n\nThis allows you to map both types with one method, like this:\n\n```Swift\nlet myIntFuture: \u003cInt, MyIntError\u003e = getThatInt()\nlet myStringFuture = myIntFuture.mapResult { (result) -\u003e Result\u003cString, MyStringError\u003e\n  switch result {\n  case .success(let firstValue):\n      if firstValue \u003c 5 {\n          return .success(\"\\(firstValue)\")\n      }\n      else {\n          return .failure(.couldntMakeString)\n      }\n  case .failure(let firstError):\n      return .failure(.couldntGetInt)\n  }\n}\n// myStringFuture will be of type Future\u003cString, MyStringError\u003e\n```\n\n##### Map Value\n\nNow, let's imagine that we have a future that goes and gets a phone number, but it gets it as an integer. Then imagine we want to fetch that, but we want it as a string. So, we want to map the value, but don't feel the need to change the error type. \n\n```Swift\n@discardableResult func mapValue\u003cNewValue\u003e(_ mapBlock:@escaping (T) -\u003e (NewValue)) -\u003e Future\u003cNewValue, E\u003e\n```\n\nThis method generates a new future with the same error type as the first, but with a new value type. So, assuming we have this first method: `getPhoneNumberInt() -\u003e Future\u003cInt, NSError\u003e`, we can easily map it like so:\n\n```Swift\nfunc getPhoneNumberString() -\u003e Future\u003cString, NSError\u003e {\n  return getPhoneNumberInt().mapValue { \"\\($0)\" }\n}\n```\n\n##### Map Error\n\nSometimes we want to pass along a value as-is, but want a more domain-specific error. For example, you might have an error that is specific to your network layer, and you want to keep that layer well encapsulated. For this, we have:\n\n```Swift\n@discardableResult func mapError\u003cNewError: Error\u003e(_ mapBlock:@escaping (E) -\u003e (NewError)) -\u003e Future\u003cT, NewError\u003e\n```\n\nSo, if we have a method like `getTweetfacePostFromNetwork(id: String) -\u003e Future\u003cTweetfacePost, NetworkError\u003e`, we can do a simple mapping to a new error type as we see fit:\n\n```Swift\nfunc getTweetfacePost(id: String) -\u003e Future\u003cTweetfacePost, TweetfaceError\u003e {\n  return getTweetfacePostFromNetwork(id: id).mapError { (networkError) -\u003e TweetfaceError in\n    switch networkError {\n    case .userError:\n      return TweetfaceError.badID\n    case .serverError:\n      return TweetfaceError.serverError\n    }\n  }\n}\n```\n\nor use the convenience `mapToNSError() -\u003e Future\u003cT, NSError\u003e` which employs `mapError(_:)` as well as Foundation's toll-free bridging of `Error` to `NSError`:\n\n```Swift\nfunc getTweetfacePost(id: String) -\u003e Future\u003cTweetfacePost, NSError\u003e {\n  return getTweetfacePostFromNetwork(id: id).mapToNSError()\n}\n```\n\n##### Flatmap\n\nIf you don't want to be bothered with doing the error-mapping yourself, *__or__* you want a failable mapping, *__or__* you want to map and have detailed information about whether it was the original error or the mapping that failed, then `flatMap(_:)` is for you!\n\n```Swift\n@discardableResult func flatMap\u003cQ\u003e(_ mapBlock:@escaping (T) -\u003e (Q?)) -\u003e Future\u003cQ, MapError\u003cT,E,Q\u003e\u003e\n```\n\nThere are a lot of scenarios where the success case of your first future may not correspond to a success case on the mapped future, and that's where `flatMap(_:)` comes in. It allows the success case of the first future to potentially trigger a failure on the second, if the result of the passed-in block returns nil. \n\nNow, you can already do this with `mapResult(_:)` and it would look something like this:\n\n```Swift\nlet getTheNumber: Future\u003cInt, IntError\u003e = getInteger()\nlet writeItDown = getTheNumber.mapResult { (result) -\u003e Result\u003cString, StringError\u003e\n  switch result {\n  case .success(let intValue):\n    if intValue \u003c 5 {\n      return .success(\"\\(intValue)\")\n    }\n    else {\n      return .failure(StringError.couldntMapInt)\n    }\n  case .failure(let error):\n    return .failure(StringError.couldntGetInt)\n  }\n}\n```\n\nHowever, this forces you to choose one error type or another, and forces you to generate an error case in your new error type that correspons to a bad mapping (seen above as `.couldntMapInt`). With flapMap, though, it's as simple as:\n\n```Swift\nlet getTheNumber: Future\u003cInt, IntError\u003e = getInteger()\nlet writeItDown = getTheNumber.flatMap { (intValue) -\u003e String?\n  return (intValue \u003c 5) ? \"\\(intValue)\" : nil\n}\n```\n\nWhat, you're surely asking now, is the error type of this new future? Good question, astute reader! The answer is that it's this handy little puppy:\n\n##### MapError\u003cT, E, Q\u003e\n\nMappError represents both possible failure states of a flat-map (which can be called on a Future *__or__* a Result). As you can see here, MapError has one case for a failure on the first future, and one other case for a failed mapping.\n\n```Swift\nenum MapError\u003cSourceType, SourceError, TargetType\u003e: Error {\n    case originalError(SourceError)\n    case mappingError(SourceType)\n}\n```\n\nAs a result, you can get all the data about a failure you need from one unified error, and it's auto-generated so that you don't have to roll one yourself. I know, I know. You're welcome. The associated value of the `.mappingError` case will be the value that was passed into the block which resulted in `nil`. And will give you a helpful debug description like: \n\n\u003e \"Could not map value: (6) to output type: String.\"\n\n#### Chaining\n\nQuite often as developers, we're presented with multiple, related bits of asynchronous work that need to be grouped together and/or connected to one another. Concurrency gives you handful of tools for dealing with these as well.\n\n##### Then\n\n```Swift\n@discardableResult func then\u003cNewValue, NewError: Error\u003e(_ mapBlock:@escaping (T)-\u003e(Future\u003cNewValue, NewError\u003e)) -\u003e Future\u003cNewValue, NewError\u003e\n```\n\nSometimes, you have bits of asynchronous work that have to happen serially, since the result of one may be needed before another can start. The `then(_:)` method allows you to do this fairly painlessly. Let's say you have to methods, one to get a phone number, `func getPhoneNumber() -\u003e Future\u003cInt, NSError\u003e`, and one to call that phone number, `func call(phoneNumber: Int) -\u003e Future\u003cPhoneCall, PhoneError\u003e`. You obviously can't call the latter until you have the result of the former. The best way to handle this is with the `then(_:)` method:\n\n```Swift\nlet makePhoneCall = getPhoneNumber().then { (number) -\u003e Future\u003cPhoneCall, PhoneError\u003e\n  return call(phoneNumber: number)\n}\n\n// Or, with the beautiful interchangeability of blocks and functions in Swift, you could write it like so:\n\nlet makePhoneCall = getPhoneNumber().then(call(phoneNumber:))\n```\n\n##### Zip\n\nPerhaps you need various bits of work to complete but they don't have to happen serially. You just need them all to be done before you can continue. Then `zip(_:)` is the tool for you! \n\n```Swift\nstatic func zip(_ futures: [Future\u003cT, E\u003e]) -\u003e Future\u003c[T], E\u003e\n```\n\nZip takes in an array of futures of type `\u003cT, E\u003e` and returns a single future with type `\u003c[T], E\u003e`. This future will resolve if/when *all* of the futures in the array have succeeded, or will be rejected if any one of them fails.\n\nFor example, let's say you wanted to fetch several phone numbers, but you need *all* of them before you can move on (a totally real-world example, _amirite?_). So, you have several methods that return `Future\u003cInt, NumberError\u003e`, and you want to perform an action once they all come back. You can do this:\n\n```Swift\nlet phoneNumberFutures: [Future\u003cInt, NumberError\u003e] = [getMomsNumber(), getDadsNumber(), getWackyPhoneNumber()]\nlet getPhoneNumbers = Future.zip(phoneNumberFutures)\n// getPhoneNumbers will be of type Future\u003c[Int], NumberError\u003e\ngetPhoneNumbers.then { (phoneNumbers) in\n  commenceZanyParentTrap(with: phoneNumbers)\n}\n```\n_Voila!_\n\n##### First Finished\n\nOn the other hand, you might have a bunch of similar tasks in process and just need the data from whichever one finishes first. There's *~~an app~~* a method for that!\n\n```Swift\nstatic func firstFinished(from futures: [Future]) -\u003e Future\n```\n\nWhichever future (if any) succeeds first will trigger the success of the joined future, and if none of them succeed, the last one to fail will trigger the error state, and pass through its error.\n\n#### One last thing...\n\nWell, two last things. Before I go, I just wanted to point out these two convenience methods on Future. They are\n\n```Swift\nstatic func preResolved(value: T) -\u003e Future\u003cT, E\u003e\n```\nand\n\n```Swift\nstatic func preRejected(error: E) -\u003e Future\u003cT, E\u003e\n```\n\nThese simply generated futures that are already complete on creation. There are two primary uses for these methods:\n\n1) Testing. This collapses several lines of setup in a test down to one.\n2) In a method which returns a future, if arguments passed in (or some other aspect of state) are insufficient to do the asynchronous work, then this gives you an option to immediately reject the future, and to do so without adding any needless asynchrony into the app.\n\n# Conclusion\n\nAnd that's about it! I hope you find all of this useful, or at least informative.\n\nI'm always tweaking code, finding optimizations, and thinking about new features. Feel free to reach out] with questions, suggestions, or (if you're super awesome) *pull requests* of your own!\n\nAll files are tested. Feel free to check out the tests [here](https://github.com/jakehawken/Concurrency/tree/master/ConcurrencyTests).\n\nAnd of course, keep your eye out for version updates!\n\n## Installation\n\nConcurrency is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```Ruby\npod \"Concurrency\"\n```\n\n## Author\n\nJake Hawken, www.github.com/jakehawken\n\n## License\n\nConcurrency 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%2Fjakehawken%2Fconcurrency","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakehawken%2Fconcurrency","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakehawken%2Fconcurrency/lists"}