一组便于在
Future
、Promise
和异步计算方面使用的类和函数。所有代码均用 Swift 编写,适用于iOS 8+
、watchOS 2
、tvOS
和Mac OS X
应用。
Pied Piper
是一组类、函数和方便的运算符,可用于在您的应用程序中 编写易于异步代码。
使用 Pied Piper
,您可以
Future
和 Promise
如果您不使用 CocoaPods,您仍然可以将 Pied Piper
作为子模块添加,将 PiedPiperSample.xcodeproj
拖放到您的项目中,并将 PiedPiper.framework
嵌入您的目标中。
PiedPiperSample.xcodeproj
拖放到您的项目中嵌入式可执行文件
部分的 +
按钮上单击PiedPiper.framework
您可以直接将所需的文件拖放到项目中,但请注意,这种方式您将无法自动获取所有最新的 Pied Piper
功能(例如,包括新操作的新文件)。
这些文件包含在 PiedPiper
文件夹中,并对 iOS
、watchOS
、MacOS
和 tvOS
框架有效。
项目包含一个小型的Xcode游乐场,让您快速了解Pied Piper
的工作原理,并试验自定义层级、层级组合以及针对请求池、配额等的不同配置。
要使用我们的游乐场,请按照以下步骤操作:
PiedPiperSample.xcodeproj
Pied Piper
框架目标,并选择一个64位平台
(例如:iPhone 6
)⌘+B
构建目标PiedPiper.playground
要运行示例项目,请克隆仓库。
Future
是一个表示尚未发生的计算的物体。您可以添加回调处理程序以处理特定Future
的成功、失败和取消事件。
请注意,Future
不是一个信号。它只能失败、成功或取消(互斥操作),并且只能进行一次。
另外,请注意Future
是“只读的”。这意味着您不能积极地确定实例的结果。如果您正在实现自己的异步计算,请参阅许诺(Promises)。
// The login function returns a Future
let login = userManager.login(username, password)
// You can specify success or failure callbacks, and chain the calls together
login.onSuccess { user in
print("User \(user.username) logged in successfully!")
}.onFailure { error in
print("Error \(error) during login")
}.onCancel {
print("Login was cancelled by the user")
}
// Or you can use onCompletion instead
login.onCompletion { result in
switch result {
case .Success(let user):
print("User \(user.username) logged in successfully!")
case .Error(let error):
print("Error \(error) during login")
case .Cancelled:
print("Login was cancelled by the user")
}
}
自Pied Piper 0.8
起,如果您已经知道结果而没有进行任何异步操作,您可以使用Future
上的便利初始化器。
let future = Future(10)
// or
let future = Future(MyError.SomeError)
// or
let future = Future(value: possiblyNil, error: MyError.SomeError)
// or
let future: Future<UIImage> = Future {
return asyncCodeThatFetchesImage()
}
Future
在您使用一些异步计算时非常方便。但有时您可能想是这些计算的提供者,在这种情况下,您需要能够确定Future
何时应该成功或失败。那么您就需要一个Promise
。
func login(username: String, password: String) -> Future<User> {
// Promises, like Futures, are generic
let promise = Promise<User>()
GCD.background {
//Request to the server...
//...
if response.statusCode == 200 {
// Success
promise.succeed(userFromResponse(response))
} else {
// Your ErrorType can be used as an argument to fail
promise.fail(LoginError.InvalidCredentials)
}
}
// If the user wants to cancel the login request
promise.onCancel {
//cancel the request to the server
//...
}
// we don't want users to be able to fail our request
return promise.future
}
Pied Piper
在GCD之上提供了几个辅助函数,可以运行代码块在主线程或后台线程。
//Without Pied Piper
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Execute your asynchronous code
// ...
let result = 10
// Notify on the main queue
dispatch_async(dispatch_get_main_queue()) {
print("Result is \(result)")
}
}
//With Pied Piper
GCD.background { Void -> Int in
// Execute your asynchronous code
return 10
}.main { result in
print("Result is \(result)")
}
您还可以在顺序队列或您自己的队列上运行异步代码。
// Serial queue
let queue = GCD.serial("test")
// Your own queue
let queue = GCD(queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))
// Then...
queue.async { Void -> Int in
return 10
}.main {
print($0)
}
Future
的高级用法自Pied Piper 0.8
以来,在Future
值上提供了许多便利函数,如map
、flatMap
、filter
、recover
、retry
、zip
、reduce
、mergeSome
和mergeAll
。此外,对于所有SequenceType
值,还提供了traverse
。
自Pied Piper 0.9
起,还提供了一些额外的函数,如snooze
、timeout
和firstCompleted
(后者的SequenceType
是Future
值)。
请注意,其中一些函数(如map
、flatMap
和filter
)也适用于Result
值。它们的工作方式与它们的Future
对应物一样。
// In this snippet `doStuff` returns a Future<Int>
let newFuture = doStuff().filter { value in
value > 0
}.flatMap { value in
Future("\(value)")
}.map { value in
"The result is \(value)"
}
// `newFuture` is now a Future<String> that will only succeed when the original Future succeeds with a value > 0
现在也可以提供“捕获所有异常”的处理程序以恢复失败的Future
。
let numberOfItemsTask: Future<Int> = doLongRunningTask()
.flatMap { result in
result.processAndPersist()
}.map { result in
result.numberOfItems
}.recover {
cache.lastNumberOfItems
}
numberOfItemsTask.onSuccess { numberOfItems in
// This will be called even if one of the previous operations fails, with the rescue value `cache.lastNumberOfItems`
// ...
}
// Example for zip
let first: Future<Int> = doFoo()
let second: Future<String> = doBar()
let zipped = first.zip(second).onSuccess { (anInteger, aString) in
// you get an Int and a String here
}
// or:
let first: Future<Int> = doFoo()
let second: Result<String> = doBar()
let zipped = first.zip(second).onSuccess { (anInteger, aString) in
// you get an Int and a String here
}
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future<Int>] = doFoo()
// With this `reduce` call we calculate the total number of items
let sumOfServerResults = serverRequests.reduce(0, combine: +).onSuccess {
// We get here only if all futures succeed
print("Sum of results is \($0)")
}
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future<Int>] = doFoo()
// 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
let allServerResults = serverRequests.mergeAll().onSuccess { results in
// We get here only if all futures succeed
// `results` is an [Int]
}
all
的行为与 mergeAll
完全相同,不同之处在于它不会携带成功值。
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future<Int>] = doFoo()
// With this `all` call we collapse the requests into one that will succeed if all of the elements succeed, otherwise it will fail
let allServerResults = serverRequests.mergeAll().onSuccess {
// We get here only if all futures succeed
}
// Let's assume this value contains a list of server requests where each request obtains the number of items in a given category
let serverRequests: [Future<Int>] = doFoo()
// With this `mergeSome` call we collapse the requests into one containing the result of just the ones that succeed
let allServerResults = serverRequests.mergeSome().onSuccess { results in
// We get here and results.count == the number of succeeded requests
// `results` is an [Int]
}
// Note: `merge` succeeds only when _all_ requests succeed, while `mergeSome` always succeeds and filters out the failed requests from the results
// 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
let serverRequests: [Future<[Product]>] = gatherRequests()
/// With this `firstCompleted` call we basically declare we are interested in only the first result and want to discard the remaining ones
let firstResult = serverRequests.firstCompleted().onSuccess { products in
// We get here with the first completing request
}
// Let's assume this list contains some product identifiers
let productIdentifiers: [Int] = basketProductsIds()
// 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
let allProductDetails = productIdentifiers.traverse({ productId in
// Let's assume this call returns a Future<Product>
ProductManager.retrieveDetailsForProduct(productId)
}).onSuccess { products in
// We get here only if all futures succeed
// `products` is a [Product]
}
// Sometimes we may be running multiple operations in parallel and we may want to have some time in between to gather multiple values
let firstOperation = doFoo()
let secondOperation = doBar()
// 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.
secondOperation.snooze(0.5).onSuccess { value in
}
// 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
let longRunningOperation = doFoo()
longRunningOperation.timeout(after: 5).onFailure { err in
if let error = err as? FutureError where error = FutureError.Timeout {
// 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)
showAlert()
}
}
longRunningOperation.onSuccess { value in
showSuccessDialog()
}
// Sometimes we want to retry a given block of code for a certain number of times before failing
retry(3, every: 0.5) {
return networkManager.fetchLatestMessages() // This returns a Future
}.onSuccess { messages in
// The operation succeeded at least once
}.onFailure { _ in
// The operation failed 4 times (1 + retry count of 3)
}
Pied Piper
当您希望在单个函数或对象中组合异步计算结果时也非常有用。
截至 Pied Piper
0.7,有 3 个公开函数
func randomInt() -> Int {
return 4 //Guaranteed random
}
func stringifyInt(number: Int) -> String {
return "\(number)"
}
func helloString(input: String) -> String {
return "Hello \(input)!"
}
let composition = randomInt >>> stringifyInt >>> helloString
composition() //Prints "Hello 4!"
如果有其中一个函数返回一个 Optional
,并且在调用时间该值是 nil
,计算将在这里停止
func randomInt() -> Int {
return 4 //Guaranteed random
}
func stringifyInt(number: Int) -> String? {
return nil
}
func helloString(input: String) -> String {
return "Hello \(input)"
}
let composition = randomInt >>> stringifyInt >>> helloString
composition() //Doesn't print
Future
func intFuture(input: Int) -> Future<Int> {
return Future(input)
}
func stringFuture(input: Int) -> Future<String> {
return Future("Hello \(input)!")
}
let composition = intFuture >>> stringFuture
composition(1).onSuccess { result in
print(result) //Prints "Hello 1!"
}
Pied Piper
已经过彻底测试,因此它设计的功能是安全的用于重构,并且尽可能无错误。
我们使用 Quick 和 Nimble 而不是 XCTest
,以便具有良好的行为驱动测试布局。
截至今天,Pied Piper
约有 600 个测试(查看 PiedPiperTests
文件夹)。
Pied Piper
正在开发中,您可以通过 此处 看到所有未解决的问题。它们被分配到了里程碑,这样您就可以了解某个特定功能何时发布。
如果您想为这个仓库做出贡献,请
如果使用 Pied Piper,请通过拉取请求告诉我们,我们将很乐意提到您的应用!
Pied Piper
由 WeltN24 内部创建。
Vittorio Monaco,[email protected],在 Github 上的@vittoriom,Twitter 上的@Vittorio_Monaco
Pied Piper
可在 MIT 许可证下使用。有关更多信息,请参阅许可证文件。
内部使用
ReadWriteLock.swift
的一些部分(特别是基于pthread的读写锁),属于Deferred(可在Github上获取)。