超简单,快速失败或者导向编程(为什么Swift要拥有所有那些闪亮的东西呢?;)).
IKResults提供了两个非常简单的类来帮助处理代码的同步和异步流程。它们不仅提高了可读性,而且也让你更容易理解其他复杂的代码路径。
Result代表了同步操作的成功或失败。
创建返回Result的函数非常简单。
-(Result *)somethingThatCouldFail {
if (failed) {
NSError *error = ...;
return [Result failure:error];
} else {
id successfulVaue = ...;
return [Result success:successfulValue];
}
}
太好了,那么这有什么帮助呢?这个方法的力量在于当你将compose
这些方法以链式方式组合在一起时。
例如,如果您正在执行具有多个步骤的操作,这些步骤可能失败,正常情况下,您可能会写出:
-(void)sometingThatHasMultipleSteps {
if ([self step1]) {
if ([self step2]) {
if ([self step3]) {
//do something once all steps succeed!
}
}
}
}
-(BOOL)step1 { ... }
-(BOOL)step2 { ... }
-(BOOL)step3 { ... }
这在大多数代码库中很常见,但这种方式的代码编写有点糟糕...首先,我们不知道哪个步骤失败了或为什么。
那么我们如何使其更简洁呢?... 您说我们可以使用指向NSError的指针?当然,让我们试一下...
-(void)sometingThatHasMultipleSteps:(NSError **)error {
if ([self step1:error]) {
if ([self step2:error]) {
if ([self step3:error]) {
//do something once all steps succeed!
}
}
}
}
-(BOOL)step1:(NSError **)error { ... }
-(BOOL)step2:(NSError **)error { ... }
-(BOOL)step3:(NSError **)error { ... }
太棒了,现在我们知道哪个步骤失败了,是什么原因了...但如果我们需要从一个步骤到下一个步骤解析除了BOOL以外的其他内容怎么办?我们需要将这些方法组合在一起...,我们可以进行另一个更改...
-(void)sometingThatHasMultipleSteps:(NSError **)error {
NSString *step1String = [self step1:error];
if (!error) {
NSString *step2String = [self step2:step1String error:error];
if (!error) {
NSString *step3String = [self step3:step2String error:error];
if (!error) {
//do something with step3String once all steps succeed!
}
}
}
}
-(NSString *)step1:(NSError **)error { ... }
-(NSString *)step2:(NSString *)input error:(NSError **)error { ... }
-(NSString *)step3:(NSString *)input error:(NSError **)error { ... }
太棒了,现在我们知道哪个步骤失败了,是什么原因,并且函数被组合在一起...但代码的右边仍然很倾斜,并且不容易阅读。
将Result
用作返回类型,您可以写出以下代码
-(void)sometingThatHasMultipleSteps {
Result *result = [self step1]
.flatMapTo(self, @selector(step2:))
.flatMapTo(self, @selector(step3:))
.success(^(id finalValue) {
//do something with finalValue
})
.failure(^(NSError *error) {
//handle any errors from any steps
});
}
-(Result *)step1 { ... }
-(Result *)step2:(NSString *)input { ... }
-(Result *)step3:(NSString *)input { ... }
使用Result我们可以简单地将一些方法组合在一起...如果任何步骤失败,则跳过链中的其余步骤,并调用带有适当错误的failure(),否则调用成功()并带有最终值。
您可能已经注意到我们没有直接解析值...实际上我们看不到任何变量!以这种风格编码使我们能够完全忘记它!.. flatMap
方法将为您处理所有这些。
使用这种风格,我们不需要担心提供指向初始NSError的指针或执行任何错误检查!而且我们去掉了右倾斜。
我们剩下的只是一个清晰的步骤列表和取决于结果的代码应该执行。
好的,这很棒...但如果我的步骤是异步的呢!我很高兴您问了...
AsyncResult 用于表示异步操作成功或失败的未来。它本质上是一个使用 Result
提供相同控制流益处的非常 基本 的 Promise 实现。
创建返回 AsyncResult 的函数和工作是一样的简单,就像 Result
。
-(AsyncResult *)somethingThatCouldFailInTheFuture {
AsyncResult *futureResult = [AsyncResult asyncResult];
dispatch_async(queue, ^{
/* perform some asynchronous task.. */
if (failed) {
NSError *error = ...;
[futureResult fulfill:[Result failure:error]];
} else {
id successfulVaue = ...;
[futureResult fulfill:[Result success:successfulValue]];
}
});
return futureResult;
}
很简单吧?这里要记住的关键点是即使异步操作仍在运行,futureResult
会被立即返回。一旦操作完成,它会用代表操作是否成功的 Result
对象来履行 futureResult
。
一旦 AsyncResult 被履行,任何通过它链式调用的方法都将像 Result
一样执行。
-(void)doSomethingInTheFuture {
[self somethingThatCouldFailInTheFuture]
.success(^(id finalValue) {
//do something with finalValue
})
.failure(^(NSError *error) {
//handle error
})
.finally(^{
//code is always executed regardless of success/failure
});
}
-(AsyncResult *)somethingThatCouldFailInTheFuture { ... }
AsyncResult
提供了一个额外的 finally()
块。
听起来不错?好吧,那么当你有一个异步方法依赖于另一个异步方法成功执行时怎么办?我相信你们都看到过这样的代码...
-(void)doAsyncTasks {
[self asyncTask1:^(id successfulValue1) {
[self asyncTask2:successfulValue1 success:^(id successfulValue2) {
//yay! do something with successfulValue2
} failure:^(NSError *error) {
//handle error..
}];
} failure:^(NSError *error) {
//handle error..
}];
}
-(void)asyncTask1:(void (^)(id successfulValue))success failure:(void (^)(NSError *error))failure { ... }
-(void)asyncTask2With:(id)input success:(void (^)(id successfulValue))success failure:(void (^)(NSError *error))failure { ... }
是的,这样是可行的...但我们确实有多个需要处理的多点故障,当然,我们可以简化它...
-(void)doAsyncTasks {
void (^errorHandler)(NSError *) = ^(NSError *error) {
//handle error..
};
[self asyncTask1:^(id successfulValue1) {
[self asyncTask2:successfulValue1 success:^(id successfulValue2) {
//yay! do something with successfulValue2
} failure:errorHandler];
} failure:errorHandler];
}
好多了!我们还有一点右对齐,但是我们已经避免了代码重复(DRY)。然而,我们现在增加了认知复杂性。由于错误处理发生在...首先,如果代码是按照顺序读取的会更好吗?
将方法返回类型设置为 AsyncResult
,你就可以编写以下代码
-(void)doAsyncTasks {
[self asyncTask1]
.flatMapAsyncSelector(self, @selector(asyncTask2:))
.success(^(id finalValue) {
//do something with finalValue
})
.failure(^(NSError *error) {
//handle any errors from any steps
});
}
-(AsyncResult *)asyncTask1 { ... }
-(AsyncResult *)asyncTask2:(id)input { ... }
那么参数是怎么解析的?!不要担心,flatMap
方法自动将一个操作的成功结果转发到下一个操作!
就像 Result
一样,这段代码可以轻松阅读,顺序执行,具有所有相同的优势。
你可以用 AsyncResult
实现一些小技巧,比如在转发给 success()
或其他异步方法之前,可以在异步方法之间传递成功值进行转化(或映射)。如果我们对之前的例子进行扩展:
-(void)doAsyncTasks {
[self asyncTask1]
.flatMapAsyncSelector(self, @selector(asyncTask2:))
.map(^NSNumber *value) {
/* for some reason we need to change the number
we got from asyncTask2: into a NSString for asyncTask3:
(don't ask me why :P .. just roll with it)
*/
return [NSString stringWithFormat:@"%@", value];
})
.flatMapAsyncSelector(self, @selector(asyncTask3:))
.success(^(id finalValue) {
//do something with finalValue
})
.failure(^(NSError *error) {
//handle any errors from any steps
});
}
-(AsyncResult *)asyncTask1 { ... }
-(AsyncResult *)asyncTask2:(NSNumber *)input { ... }
-(AsyncResult *)asyncTask3:(NSString *)input { ... }
在这里,我们很容易地添加了第三个异步操作,并且在 asyncTask2:
和 asyncTask3:
之间轻松地对值进行了转换!
或者手动将 Result.(h|m) 和 AsyncResult.(h|m) 添加到你的项目中
欢迎发起 Pull Requests!
如果你在这个项目中使用了它,我会很乐意听到!即使这仅仅帮助你对这种编程方式的好处有更好的理解!
如果你想查看更完整的 Promise 实现,请查看惊人的 PromiseKit。他们还有出色的文档!
我通常在 iOS Developers 上闲逛。你应该去了解一下!