{"id":2130635,"url":"https://github.com/spring-media/PiedPiper","last_synced_at":"2025-07-13T11:30:32.113Z","repository":{"id":62450743,"uuid":"58852188","full_name":"spring-media/PiedPiper","owner":"spring-media","description":"A small set of classes and functions to make easy use of Futures, Promises and async computation in general. All written in Swift for iOS 10+, WatchOS 3, tvOS and Mac OS X apps.","archived":true,"fork":false,"pushed_at":"2023-09-18T09:15:56.000Z","size":1042,"stargazers_count":44,"open_issues_count":10,"forks_count":10,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-07-05T04:13:50.534Z","etag":null,"topics":["apps","promise","swift","swift-package-manager"],"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/spring-media.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-05-15T08:49:37.000Z","updated_at":"2024-08-02T10:02:26.000Z","dependencies_parsed_at":"2022-11-01T23:33:12.053Z","dependency_job_id":"e52d4e34-236b-407a-98d6-5309f77cbd3c","html_url":"https://github.com/spring-media/PiedPiper","commit_stats":null,"previous_names":["weltn24/piedpiper"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/spring-media/PiedPiper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2FPiedPiper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2FPiedPiper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2FPiedPiper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2FPiedPiper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spring-media","download_url":"https://codeload.github.com/spring-media/PiedPiper/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spring-media%2FPiedPiper/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265131449,"owners_count":23716015,"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":["apps","promise","swift","swift-package-manager"],"created_at":"2024-01-21T23:24:37.060Z","updated_at":"2025-07-13T11:30:31.826Z","avatar_url":"https://github.com/spring-media.png","language":"Swift","readme":"# Pied Piper [Deprecated]\n\n[![Build Status](https://travis-ci.org/spring-media/PiedPiper.svg?branch=master)](https://travis-ci.org/spring-media/PiedPiper)\n[![Version](https://img.shields.io/cocoapods/v/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![License](https://img.shields.io/cocoapods/l/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)\n[![Platform](https://img.shields.io/cocoapods/p/PiedPiper.svg?style=flat)](http://cocoapods.org/pods/PiedPiper)\n\n---\n**NOTE**\n\nPiedPiper is no longer supported. We suggest to use [Combine](https://developer.apple.com/documentation/combine)framework if you target iOS 13+ or [OpenCombine](https://github.com/OpenCombine/OpenCombine) if you target iOS 12 and lower.\n\n---\n\n\u003e A small set of classes and functions to make easy use of `Future`s, `Promise`s and async computation in general. All written in Swift for `iOS 10`, `WatchOS 3`, `tvOS` and `Mac OS X` apps.\n\n# Contents of this Readme\n\n- [What is Pied Piper?](#what-is-pied-piper)\n- [Installation](#installation)\n- [Playground](#playground)\n- [Requirements](#requirements)\n- [Usage](#usage)\n\t- [Usage examples](#usage-examples)\n\t- [Futures](#futures)\n\t- [Promises](#promises)\n\t- [GCD Computation](#gcd-computation)\n\t- [Advanced usage with Futures](#advanced-usage-with-futures)\n\t- [Function composition](#function-composition)\n- [Tests](#tests)\n- [Future development](#future-development)\n- [Apps using Pied Piper](#apps-using-pied-piper)\n- [Authors](#authors)\n- [License](#license)\n- [Acknowledgements](#acknowledgements)\n\n## What is Pied Piper?\n\n`Pied Piper` is a small set of classes, functions and convenience operators to **write easy asynchronous code** in your application.\n\nWith `Pied Piper` you can:\n\n- Use [`Future`](#futures)s and [`Promise`](#promises)s for your async tasks\n- Write easy to read async code [on top of GCD](#gcd-computation)\n- [Compose asynchronous functions](#function-composition) in an intuitive way\n\n## Installation\n\n### CocoaPods\n\n`Pied Piper` is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```\npod \"PiedPiper\"\n```\n\n### Submodule\n\nIf you don't use CocoaPods, you can still add `Pied Piper` as a submodule, drag and drop `PiedPiperSample.xcodeproj` into your project, and embed `PiedPiper.framework` in your target.\n\n- Drag `PiedPiperSample.xcodeproj` to your project\n- Select your app target\n- Click the `+` button on the `Embedded binaries` section\n- Add `PiedPiper.framework`\n\n### Carthage\n\n`Carthage` is also supported.\n\n### Manual\n\nYou can directly drag and drop the needed files into your project, but keep in mind that this way you won't be able to automatically get all the latest `Pied Piper` features (e.g. new files including new operations).\n\nThe files are contained in the `PiedPiper` folder and work for the `iOS`, `watchOS`, `MacOS` and `tvOS` frameworks.\n\n## Playground\n\nWe ship a small Xcode Playground with the project, so you can quickly see how `Pied Piper` works and experiment with your custom layers, layers combinations and different configurations for requests pooling, capping, etc.\n\nTo use our Playground, please follow these steps:\n\n- Open the Xcode project `PiedPiperSample.xcodeproj`\n- Select the `Pied Piper` framework target, and a **64-bit platform** (e.g. `iPhone 6`)\n- Build the target with `⌘+B`\n- Click the Playground file `PiedPiper.playground`\n- Write your code\n\n## Requirements\n\n- iOS 10.0+\n- WatchOS 3+\n- Mac OS X 10.12+\n- tvOS 10+\n\n## Usage\n\nTo run the example project, clone the repo.\n\n### Usage examples\n\n### Futures\n\nA `Future` is an object representing a computation that may not have happened yet. You can add callback handlers for the success, failure, and cancelation events of a specific `Future`.\n\nPlease keep in mind that a `Future` is not a signal. It can only fail, succeed or cancel (mutually exclusive) and it can only do so once.\n\nPlease also note that a `Future` is \"read-only\". This means you can not actively determine the result of an instance. If you are implementing your own asynchronous computations, please have a look at [promises](#promises) instead.\n\n```swift\n// The login function returns a Future\nlet login = userManager.login(username, password)\n\n// You can specify success or failure callbacks, and chain the calls together\nlogin.onSuccess { user in\n  print(\"User \\(user.username) logged in successfully!\")\n}.onFailure { error in\n  print(\"Error \\(error) during login\")\n}.onCancel {\n  print(\"Login was cancelled by the user\")\n}\n\n// Or you can use onCompletion instead\nlogin.onCompletion { result in\n  switch result {\n  case .Success(let user):\n  \tprint(\"User \\(user.username) logged in successfully!\")\n  case .Error(let error):\n    print(\"Error \\(error) during login\")\n  case .Cancelled:\n    print(\"Login was cancelled by the user\")\n  }\n}\n```\n\nSince `Pied Piper 0.8` you can use convenience initializers on `Future` if you already know the result without any asynchronous work:\n\n```swift\nlet future = Future(10)\n\n// or\n\nlet future = Future(MyError.SomeError)\n\n// or\n\nlet future = Future(value: possiblyNil, error: MyError.SomeError)\n\n// or\n\nlet future: Future\u003cUIImage\u003e = Future {\n\treturn asyncCodeThatFetchesImage()\n}\n```\n\n### Promises\n\n`Future`s are really handy when you are the user of some async computations. But sometimes you may want to be the producer of these, and in this case you need to be able to determine when a `Future` should succeed or fail. Then you need a `Promise`.\n\n```swift\nfunc login(username: String, password: String) -\u003e Future\u003cUser\u003e {\n  // Promises, like Futures, are generic\n  let promise = Promise\u003cUser\u003e()\n\n  GCD.background {\n    //Request to the server...\n    //...\n    if response.statusCode == 200 {\n      // Success\n      promise.succeed(userFromResponse(response))\n    } else {\n      // Your ErrorType can be used as an argument to fail\n      promise.fail(LoginError.InvalidCredentials)\n    }\n  }\n\n  // If the user wants to cancel the login request\n  promise.onCancel {\n    //cancel the request to the server\n    //...\n  }\n\n  // we don't want users to be able to fail our request\n  return promise.future\n}\n```\n\n### GCD computation\n\n`Pied Piper` offers some helper functions on top of GCD to run blocks of code on the main queue or on a background queue.\n\n```swift\n//Without Pied Piper\ndispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {\n  // Execute your asynchronous code\n  // ...\n  let result = 10\n\n  // Notify on the main queue\n  dispatch_async(dispatch_get_main_queue()) {\n    print(\"Result is \\(result)\")\n  }\n}\n\n//With Pied Piper\nGCD.background { Void -\u003e Int in\n  // Execute your asynchronous code\n  return 10\n}.main { result in\n  print(\"Result is \\(result)\")\n}\n```\n\nYou can also run your asynchronous code on serial queues or your own queues.\n\n```swift\n// Serial queue\nlet queue = GCD.serial(\"test\")\n\n// Your own queue\nlet queue = GCD(queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))\n\n// Then...\nqueue.async { Void -\u003e Int in\n  return 10\n}.main {\n  print($0)\n}\n```\n\n### Advanced usage with Futures\n\nSince `Pied Piper 0.8` many convenience functions are available on `Future` values, like `map`, `flatMap`, `filter`, `recover`, `retry`, `zip`, `reduce`, `mergeSome` and `mergeAll`. Moreover, `traverse` is available for all `SequenceType` values.\n\nSince `Pied Piper 0.9` some more functions are available like `snooze`, `timeout` and `firstCompleted` (the latter for a `SequenceType` of `Future` values).\n\nKeep in mind that some of these functions (`map`, `flatMap` and `filter`) are also available on `Result` values. They work just like their `Future` counterparts.\n\n#### FlatMap, Map, Filter\n\n```swift\n// In this snippet `doStuff` returns a Future\u003cInt\u003e\nlet newFuture = doStuff().filter { value in\n  value \u003e 0\n}.flatMap { value in\n  Future(\"\\(value)\")\n}.map { value in\n  \"The result is \\(value)\"\n}\n\n// `newFuture` is now a Future\u003cString\u003e that will only succeed when the original Future succeeds with a value \u003e 0\n```\n\n#### Recover\n\nIt's now also possible to provide a \"catch-all\" handler to recover a failing `Future`:\n\n```swift\nlet numberOfItemsTask: Future\u003cInt\u003e = doLongRunningTask()\n  .flatMap { result in\n    result.processAndPersist()\n  }.map { result in\n    result.numberOfItems\n  }.recover {\n    cache.lastNumberOfItems\n  }\n\nnumberOfItemsTask.onSuccess { numberOfItems in\n  // This will be called even if one of the previous operations fails, with the rescue value `cache.lastNumberOfItems`\n  // ...\n}\n```\n\n#### Zip\n\n```swift\n// Example for zip\n\nlet first: Future\u003cInt\u003e = doFoo()\nlet second: Future\u003cString\u003e = doBar()\n\nlet zipped = first.zip(second).onSuccess { (anInteger, aString) in\n  // you get an Int and a String here\n}\n\n// or:\n\nlet first: Future\u003cInt\u003e = doFoo()\nlet second: Result\u003cString\u003e = doBar()\n\nlet zipped = first.zip(second).onSuccess { (anInteger, aString) in\n  // you get an Int and a String here\n}\n```\n\n#### Reduce\n\n```swift\n// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category\nlet serverRequests: [Future\u003cInt\u003e] = doFoo()\n\n// With this `reduce` call we calculate the total number of items\nlet sumOfServerResults = serverRequests.reduce(0, combine: +).onSuccess {\n  // We get here only if all futures succeed\n  print(\"Sum of results is \\($0)\")\n}\n```\n\n#### MergeAll\n\n```swift\n// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category\nlet serverRequests: [Future\u003cInt\u003e] = doFoo()\n\n// With this `mergeAll` call we collapse the requests into one containing the result of all of them, if they all succeeded, or none if one fails\nlet allServerResults = serverRequests.mergeAll().onSuccess { results in\n  // We get here only if all futures succeed\n  // `results` is an [Int]\n}\n```\n\n#### All\n\n`all` behaves exactly like `mergeAll`, except that it doesn't bring the success values with it.\n\n```swift\n// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category\nlet serverRequests: [Future\u003cInt\u003e] = doFoo()\n\n// With this `all` call we collapse the requests into one that will succeed if all of the elements succeed, otherwise it will fail\nlet allServerResults = serverRequests.mergeAll().onSuccess {\n  // We get here only if all futures succeed\n}\n```\n\n#### MergeSome\n\n```swift\n// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category\nlet serverRequests: [Future\u003cInt\u003e] = doFoo()\n\n// With this `mergeSome` call we collapse the requests into one containing the result of just the ones that succeed\nlet allServerResults = serverRequests.mergeSome().onSuccess { results in\n  // We get here and results.count == the number of succeeded requests\n  // `results` is an [Int]\n}\n\n// Note: `merge` succeeds only when _all_ requests succeed, while `mergeSome` always succeeds and filters out the failed requests from the results\n```\n\n#### FirstCompleted\n\n```swift\n// Let's assume this value contains a list of server requests where each request comes from a different server but all of them answer the same question\nlet serverRequests: [Future\u003c[Product]\u003e] = gatherRequests()\n\n/// With this `firstCompleted` call we basically declare we are interested in only the first result and want to discard the remaining ones\nlet firstResult = serverRequests.firstCompleted().onSuccess { products in\n  // We get here with the first completing request\n}\n```\n\n#### Traverse\n\n```swift\n// Let's assume this list contains some product identifiers\nlet productIdentifiers: [Int] = basketProductsIds()\n\n// With this `traverse` call we create a Future for every identifier (for instance to retrieve details of each product), and we merge the results into one final Future\nlet allProductDetails = productIdentifiers.traverse({ productId in\n  // Let's assume this call returns a Future\u003cProduct\u003e\n  ProductManager.retrieveDetailsForProduct(productId)\n}).onSuccess { products in\n  // We get here only if all futures succeed\n  // `products` is a [Product]\n}\n```\n\n#### Snooze\n\n```swift\n// Sometimes we may be running multiple operations in parallel and we may want to have some time in between to gather multiple values\nlet firstOperation = doFoo()\nlet secondOperation = doBar()\n\n// With this call to `snooze` we declare we're not interested in immediate feedback from the second operation because we may want to process the result of the first, first.\nsecondOperation.snooze(0.5).onSuccess { value in\n}\n```\n\n#### Timeout\n\n```swift\n// Sometimes we may want to set an upper bound to the time an operation can run before moving on or showing something to the user\nlet longRunningOperation = doFoo()\n\nlongRunningOperation.timeout(after: 5).onFailure { err in\n  if let error = err as? FutureError where error = FutureError.Timeout {\n    // The operation timed out, but of course it's still running. We may keep adding observers to the original variable if we are still interested in the final result (see next lines)\n    showAlert()\n  }\n}\n\nlongRunningOperation.onSuccess { value in\n  showSuccessDialog()\n}\n\n```\n\n#### Retry\n\n```swift\n// Sometimes we want to retry a given block of code for a certain number of times before failing\nretry(3, every: 0.5) {\n  return networkManager.fetchLatestMessages() // This returns a Future\n}.onSuccess { messages in\n  // The operation succeeded at least once\n}.onFailure { _ in\n  // The operation failed 4 times (1 + retry count of 3)\n}\n```\n\n### Function composition\n\n`Pied Piper` can also be helpful when you want to compose the result of asynchronous computation in a single function or object.\n\nThere are 3 public functions as of `Pied Piper` 0.7:\n\n- Compose functions\n\n```swift\nfunc randomInt() -\u003e Int {\n  return 4 //Guaranteed random\n}\n\nfunc stringifyInt(number: Int) -\u003e String {\n  return \"\\(number)\"\n}\n\nfunc helloString(input: String) -\u003e String {\n  return \"Hello \\(input)!\"\n}\n\nlet composition = randomInt \u003e\u003e\u003e stringifyInt \u003e\u003e\u003e helloString\n\ncomposition() //Prints \"Hello 4!\"\n```\n\nIf one of the functions returns an `Optional`, and at call time the value is `nil`, the computation stops there:\n\n```swift\n\nfunc randomInt() -\u003e Int {\n  return 4 //Guaranteed random\n}\n\nfunc stringifyInt(number: Int) -\u003e String? {\n  return nil\n}\n\nfunc helloString(input: String) -\u003e String {\n  return \"Hello \\(input)\"\n}\n\nlet composition = randomInt \u003e\u003e\u003e stringifyInt \u003e\u003e\u003e helloString\n\ncomposition() //Doesn't print\n```\n\n- Compose `Future`s:\n\n```swift\n\nfunc intFuture(input: Int) -\u003e Future\u003cInt\u003e {\n  return Future(input)\n}\n\nfunc stringFuture(input: Int) -\u003e Future\u003cString\u003e {\n  return Future(\"Hello \\(input)!\")\n}\n\nlet composition = intFuture \u003e\u003e\u003e stringFuture\n\ncomposition(1).onSuccess { result in\n  print(result) //Prints \"Hello 1!\"\n}\n```\n\n## Tests\n\n`Pied Piper` is thouroughly tested so that the features it's designed to provide are safe for refactoring and as bug-free as possible.\n\nWe use [Quick](https://github.com/Quick/Quick) and [Nimble](https://github.com/Quick/Nimble) instead of `XCTest` in order to have a good BDD test layout.\n\nAs of today, there are around **600 tests** for `Pied Piper` (see the folder `Tests`).\n\n## Future development\n\n`Pied Piper` is under development and [here](https://github.com/spring-media/PiedPiper/issues) you can see all the open issues. They are assigned to milestones so that you can have an idea of when a given feature will be shipped.\n\nIf you want to contribute to this repo, please:\n\n- Create an issue explaining your problem and your solution\n- Clone the repo on your local machine\n- Create a branch with the issue number and a short abstract of the feature name\n- Implement your solution\n- Write tests (untested features won't be merged)\n- When all the tests are written and green, create a pull request, with a short description of the approach taken\n\n## Apps using Pied Piper\n\n- [Die Welt Edition](https://itunes.apple.com/de/app/welt-edition-digitale-zeitung/id372746348?mt=8)\n\nUsing Pied Piper? Please let us know through a Pull request, we'll be happy to mention your app!\n\n## Authors\n\n`Pied Piper` was made in-house by WeltN24\n\n### Contributors:\n\nVittorio Monaco, [vittorio.monaco@weltn24.de](mailto:vittorio.monaco@weltn24.de), [@vittoriom](https://github.com/vittoriom) on Github, [@Vittorio_Monaco](https://twitter.com/Vittorio_Monaco) on Twitter\n\n## License\n\n`Pied Piper` is available under the MIT license. See the LICENSE file for more info.\n\n## Acknowledgements\n\n`Pied Piper` internally uses:\n\n- Some parts of `ReadWriteLock.swift` (in particular the pthread-based read-write lock) belonging to **Deferred** (available on [Github](https://github.com/bignerdranch/Deferred))\n","funding_links":[],"categories":["HarmonyOS"],"sub_categories":["Windows Manager"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspring-media%2FPiedPiper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspring-media%2FPiedPiper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspring-media%2FPiedPiper/lists"}