DRBOperationTree 是 CocoaPods 上的一个 Objective-C/iOS 和 OSX API,用于将工作(NSOperations)组织成一个树,让每个父级的输出传递给其子级以进行进一步处理。
让我们假设我们有一个 API,具有以下端点
/cookbook/{cookbook_id} | 提供 recipe id 的列表 |
/recipes/{recipe_id} | 提供 recipe 的 JSON 表示形式 |
/ingredients/{ingredient_id} | 提供 ingredient 的 JSON 表示形式 |
/images/{image_id} | 提供 PNG 图像 |
为了序列化 cookbook 的对象图,我们需要从 /cookbook/{cookbook_id}
端点获取 recipe id 的列表,然后获取每个 recipe,然后是每个 recipe 的每个图像和每个 ingredient。每个步骤中请求数取决于前一个请求的响应。例如,为了知道我们将要发起多少(以及多少个)/ingredients/{ingredient_id}
请求,我们必须首先获取并解析 /recipes/{recipe_id}/ingredient
。我们可以将这些依赖关系建模为树。
如果我们需要对蔬菜炖菜 recipe 的对象图进行序列化,我们发起的请求树可能如下所示
假设我们正在开发一个 iOS 应用,它将在 cookbook 中显示所有 recipe。对于这个例子,主视图是一个包含图片和简短食材列表的 recipe 列表。为了显示 recipe,我们需要序列化 recipe 以及所有子对象。我们不想让用户在请求完成之前等待,所以让我们在 recipe 一旦准备好就显示给用户。这意味着我们将按层序遍历请求树,并在可能的情况下并行发起请求。我们可以尝试以下方法
[self fetchCookbook:cookbookID completion:^(id cookbook) {
for (id recipeID in cookbook.recipeIDs) {
[self fetchRecipe:recipeID completion:^{id recipe) {
for (id ingredientID in recipe.ingredientIDs) {
[self fetchIngredient:ingredientID completion:^(id ingredient) {
for (id imageID in ingredient.imageIDs) {
[recipe addIngredient:ingredient];
[self fetchImage:imageID completion:^(id image) {
[ingredient addImage:image];
}];
}
[recipe addIngredient:ingredient];
}];
}
}];
for (id imageID in recipe.images) {
[self fetchImage:imageID completion:^(id image) {
[recipe addImage:image];
}];
}
}
}];
假设所有我们的获取方法都允许并发请求,这将实现我们的目标,但代码不是很理想。我们可以清理代码,但是还有一个问题需要解决:我们需要知道何时所有子对象都已序列化。在我们的请求树中,我们有两个叶节点集:recipe 图像和 ingredient 图像。在上面的方法中,我们需要添加代码来检测两组异步请求何时完成。
另一种方法会考虑到上述所有代码都遵循以下模式
DRBOperationTree 正确采取了这种方法。DRBOperationTree 允许我们将依赖关系定义为一个树,然后按照层级顺序执行每个节点对应的“工作”。当一个节点的所有后序遍历都完成时,该节点被标记为完成。以下是将上述代码重构为使用 DRBOperationTree 的方法。
DRBOperationTree *cookbook = [DRBOperationTree tree];
DRBOperationTree *recipe = [DRBOperationTree tree];
DRBOperationTree *recipeImage = [DRBOperationTree tree];
DRBOperationTree *ingredient = [DRBOperationTree tree];
DRBOperationTree *ingredientImage = [DRBOperationTree tree];
recipe.provider = [[RecipeProvider alloc] init];
recipeImage.provider = [[RecipeImageProvider alloc] init];
ingredient.provider = [[IngredientProvider alloc] init];
ingredientImage.provider = [[IngredientImageProvider alloc] init];
[cookbook addChild:recipe];
[recipe addChild:recipeImage];
[recipe addChild:ingredient];
[ingredient addChild:ingredientImage];
[cookbook sendObject:@"a-cookbook" completion:^{
// all done
}];
树中的每个节点都将它的输出发送给它的子节点。在这个例子中,配方节点将序列化的配方对象发送给原料节点。ingredient.provider 负责将传入的配方对象映射到输出的原料对象。然后它创建 NSOperations,用于下载和序列化每个原料对象。提供者对象遵守 DRBOperationProvider 协议,该协议具有以下两个方法
// maps input objects to output objects (ex. recipe -> ingredient ids)
- (void)operationTree:(DRBOperationTree *)node
objectsForObject:(id)object
completion:(void(^)(NSArray *objects))completion {
// this method is optionally asynchronous
// in this example, we're just mapping a recipe to it's child ingredient ids
completion(recipe.ingredientIDs);
}
// given an object, returns an operation for that object and passes along the result
// (ex. ingredient id -> operation to fetch ingredient -> serialized ingredient object)
- (NSOperation *)operationTree:(DRBOperationTree *)node
operationForObject:(id)object
success:(void(^)(id object))success
failure:(void(^)())failure {
return [NSBlockOperation blockOperationWithBlock:^{
[self fetchIngredient:object completion:success];
}];
}
使用 DRBOperationTree 的这种方法,我们
DRBOperation 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。