后台有点复杂,所以这里是我要做的:
>让用户拍照,输入标题并授权地图使用其位置
>为帖子生成一个唯一的标识符
>在后台创建帖子
>上传图片
>刷新UI
我使用了几个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中的消息
>等
你如何管理这样的工作流?我想避免嵌入多个完成块,我觉得NSOperation真的是要走的路,但我也觉得在某个地方有更好的实现.
谢谢!
解决方法
完成这个很容易.其中一个大目标就是制造这样的一个
构成微不足道
如果您以前没有听说过ReactiveCocoa,或者不熟悉它,请检查
出Introduction
为了快速解释.
我将避免在这里重复整个框架概述,但足以说出来
RAC实际上提供了一个承诺/期货的超集.它允许你组合和
完全不同起源的转换事件(UI,网络,数据库,KVO,
通知等),这是非常强大的.
要开始RACify这个代码,我们可以做的第一个也是最简单的事情
这些单独的操作变成方法,并确保每个操作都返回
RAC信号.这并不是绝对必要的(它们都可以在其中定义
一个范围),但它使代码更加模块化和可读性.
例如,让我们创建一个对应的进程和对应的信号
generateFilename:
- (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的例子了解更多关于如何帮助.