IKResults 1.2

IKResults 1.2

测试已测试
Lang语言 Obj-CObjective C
许可 MIT
发布最新发布2016年2月

Ian Keen维护。



  • 作者:
  • Ian Keen

超简单,快速失败或者导向编程(为什么Swift要拥有所有那些闪亮的东西呢?;)).

IKResults提供了两个非常简单的类来帮助处理代码的同步和异步流程。它们不仅提高了可读性,而且也让你更容易理解其他复杂的代码路径。

Result

Result代表了同步操作的成功或失败。

创建一个Result

创建返回Result的函数非常简单。

-(Result *)somethingThatCouldFail {
    if (failed) {
        NSError *error = ...;
        return [Result failure:error];

    } else {
        id successfulVaue = ...;
        return [Result success:successfulValue];
    }
}

太好了,那么这有什么帮助呢?这个方法的力量在于当你将compose这些方法以链式方式组合在一起时。

使用Result组合方法

例如,如果您正在执行具有多个步骤的操作,这些步骤可能失败,正常情况下,您可能会写出:

-(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

AsyncResult 用于表示异步操作成功或失败的未来。它本质上是一个使用 Result 提供相同控制流益处的非常 基本 的 Promise 实现。

创建 AsyncResult

创建返回 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() 块。

使用 AsyncResult 组合异步方法

听起来不错?好吧,那么当你有一个异步方法依赖于另一个异步方法成功执行时怎么办?我相信你们都看到过这样的代码...

-(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 上闲逛。你应该去了解一下!