我正在开发一个创建内容并将其发送到现有后端的应用程序。内容是标题、图片和位置。没什么特别的。
后端有点复杂,所以这是我必须做的:
- 让用户拍照、输入标题并授权 map 使用其位置
- 为帖子生成唯一标识符
- 在后端创建帖子
- 上传图片
- 刷新用户界面
我使用了几个 NSOperation 子类来完成这项工作,但我对我的代码并不感到自豪,这里有一个示例。
NSOperation *process = [NSBlockOperation blockOperationWithBlock:^{
// Process image before upload
}];
NSOperation *filename = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(generateFilename) object: nil];
NSOperation *generateEntry = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(createEntry) object: nil];
NSOperation *uploadImage = [[NSInvocationOperation alloc] initWithTarget: self selector: @selector(uploadImageToCreatedEntry) object: nil];
NSOperation *refresh = [NSBlockOperation blockOperationWithBlock:^{
// Update UI
[SVProgressHUD showSuccessWithStatus: NSLocalizedString(@"Success!", @"Success HUD message")];
}];
[refresh addDependency: uploadImage];
[uploadImage addDependency: generateEntry];
[generateEntry addDependency: filename];
[generateEntry addDependency: process];
[[NSOperationQueue mainQueue] addOperation: refresh];
[_queue addOperations: @[uploadImage, generateEntry, filename, process] waitUntilFinished: NO];
以下是我不喜欢的东西:
- 在我的 createEntry 中:例如,我将生成的文件名存储在一个属性中,该属性符合我的类的全局范围
- 在 uploadImageToCreatedEntry: 方法中,我使用 dispatch_async + dispatch_get_main_queue() 来更新我的 HUD 中的消息
- 等等
您将如何管理这样的工作流程?我想避免嵌入多个完成 block ,我觉得 NSOperation 确实是可行的方法,但我也觉得某处有更好的实现。
谢谢!
最佳答案
您可以使用 ReactiveCocoa至 很容易做到这一点。它的一大目标是使这种 组成琐碎。
如果您之前没有听说过 ReactiveCocoa,或者不熟悉它,请查看 出Introduction 快速解释。
我将避免在此处重复整个框架概述,但足以说明 RAC 实际上提供了 promise / future 的超集。它允许您编写和 转换完全不同来源的事件(UI、网络、数据库、KVO、 通知等),功能非常强大。
要开始 RACifying 这段代码,我们可以做的第一件也是最简单的事情就是把
这些单独的操作放入方法中,并确保每一个都返回
RACSignal
。这不是绝对必要的(它们都可以在
一个范围),但它使代码更加模块化和可读性。
例如,让我们创建一对信号对应于 process
和
生成文件名
:
- (RACSignal *)processImage:(UIImage *)image {
return [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
// Process image before upload
UIImage *processedImage = …;
[subscriber sendNext:processedImage];
[subscriber sendCompleted];
}];
}
- (RACSignal *)generateFilename {
return [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) {
NSString *filename = [self generateFilename];
[subscriber sendNext:filename];
[subscriber sendCompleted];
}];
}
其他操作(createEntry
和uploadImageToCreatedEntry
)非常相似。
一旦我们有了这些,就很容易组合它们并表达它们 依赖项(尽管注释使它看起来有点密集):
[[[[[[self
generateFilename]
flattenMap:^(NSString *filename) {
// Returns a signal representing the entry creation.
// We assume that this will eventually send an `Entry` object.
return [self createEntryWithFilename:filename];
}]
// Combine the value with that returned by `-processImage:`.
zipWith:[self processImage:startingImage]]
flattenMap:^(RACTuple *entryAndImage) {
// Here, we unpack the zipped values then return a single object,
// which is just a signal representing the upload.
return [self uploadImage:entryAndImage[1] toCreatedEntry:entryAndImage[0]];
}]
// Make sure that the next code runs on the main thread.
deliverOn:RACScheduler.mainThreadScheduler]
subscribeError:^(NSError *error) {
// Any errors will trickle down into this block, where we can
// display them.
[self presentError:error];
} completed:^{
// Update UI
[SVProgressHUD showSuccessWithStatus: NSLocalizedString(@"Success!", @"Success HUD message")];
}];
请注意,我重命名了您的一些方法,以便它们可以接受来自 它们的依赖关系,为我们提供了一种更自然的方式来从一个 进行下一步操作。
这里有巨大的优势:
- 你可以自上而下阅读,所以很容易理解顺序 事情发生在什么地方,依赖在哪里。
- 在不同的线程之间移动工作非常容易,这证明了这一点
使用
-deliverOn:
。 - 这些方法中任何发送的任何错误都会自动取消所有
剩下的 工作,并最终到达
subscribeError:
block 处理。 - 您还可以将其与其他事件流组合(即,不仅仅是 操作)。例如,您可以将其设置为仅在出现 UI 时触发 信号(如按钮点击)触发。
ReactiveCocoa 是一个巨大的框架,不幸的是很难提炼出 优势归结为一个小的代码示例。我强烈建议您查看 when to use ReactiveCocoa 的示例 详细了解它如何提供帮助。
关于ios - 管理一堆具有依赖关系的 NSOperation,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18876866/