ios - 管理一堆具有依赖关系的 NSOperation

标签 ios objective-c nsoperation nsoperationqueue

我正在开发一个创建内容并将其发送到现有后端的应用程序。内容是标题、图片和位置。没什么特别的。

后端有点复杂,所以这是我必须做的:

  • 让用户拍照、输入标题并授权 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];
    }];
}

其他操作(createEntryuploadImageToCreatedEntry)非常相似。

一旦我们有了这些,就很容易组合它们并表达它们 依赖项(尽管注释使它看起来有点密集):

[[[[[[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/

相关文章:

ios - 绘制连接的三角形条 (ios 6) 时出现倒三角条(缠绕问题)

ios - Swift:如何压缩多个 NSData 对象以进行进一步传输(Airdrop、电子邮件、Dropbox ...)

objective-c - 应用程序试图在目标 <MFMessageComposeViewController : 0x96d1380> 上推送一个 nil View Controller

swift - 如何在并发模式下从多个 URL 下载数据?

objective-c - 在当前 View 中访问 subview

ios - 点击标签栏时将表格 View 滚动到顶部

objective-c - 百分比符号 (%) 在 Objective C 中的数学作用是什么?

objective-c - 当用户点击 iPhone 屏幕上的任意位置时,iOS7 中的 UIActionSheet 将被关闭

iphone - NSOperation 和 SetImage

ios - NSOperation 中的 ASINetworkQueue 错误保存核心数据