合并框架:如何在继续进行之前异步处理数组的每个元素 [英] Combine framework: how to process each element of array asynchronously before proceeding

查看:66
本文介绍了合并框架:如何在继续进行之前异步处理数组的每个元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用iOS Combine框架时,我有些不安.

I'm having a bit of a mental block using the iOS Combine framework.

我正在将一些代码从从远程API的手动"获取转换为使用Combine.基本上,API是SQL和REST(实际上是Salesforce,但这与问题无关).用于执行代码的是调用采用完成处理程序的REST查询方法.我正在做的是在任何地方都用Combine Future取代它.到目前为止,一切都很好.

I'm converting some code from "manual" fetching from a remote API to using Combine. Basically, the API is SQL and REST (in actual fact it's Salesforce, but that's irrelevant to the question). What the code used to do is call a REST query method that takes a completion handler. What I'm doing is replacing this everywhere with a Combine Future. So far, so good.

以下情况发生时(很多情况下)会出现问题:

The problem arises when the following scenario happens (and it happens a lot):

  1. 我们执行REST查询并获取对象"数组.

  1. We do a REST query and get back an array of "objects".

但是这些对象"没有完全填充.他们每个人都需要来自某个相关对象的其他数据.因此,对于每个对象",我们都使用该对象"中的信息进行另一个REST查询,从而为我们提供了"<对象>"的另一个数组.

But these "objects" are not completely populated. Each one of them needs additional data from some related object. So for each "object", we do another REST query using information from that "object", thus giving us another array of "objects".

这可能会或可能不会使我们无法完成第一个对象"的填充-否则,我们可能不得不使用来自每个秒的信息进行另一个 REST查询对象",等等.

This might or might not allow us to finish populating the first "objects" — or else, we might have to do another REST query using information from each of the second "object", and so on.

结果是很多这样的代码结构(这是伪代码):

The result was a lot of code structured like this (this is pseudocode):

func fetchObjects(completion: @escaping ([Object] -> Void) {
    let restQuery = ...
    RESTClient.performQuery(restQuery) { results in
        let partialObjects = results.map { ... }
        let group = DispatchGroup()
        for partialObject in partialObjects {
            let restQuery = ... // something based on partialObject
            group.enter()
            RESTClient.performQuery(restQuery) { results in
                group.leave()
                let partialObjects2 = results.map { ... }
                partialObject.property1 = // something from partialObjects2
                partialObject.property2 = // something from partialObjects2
                // and we could go down yet _another_ level in some cases
            }
        }
        group.notify {
            completion([partialObjects])
        }
    }
}

每次我在伪代码中说results in时,这就是异步网络调用的完成处理程序.

Every time I say results in in the pseudocode, that's the completion handler of an asynchronous networking call.

好的,我很好地了解了如何在Combine中链接异步调用,例如通过使用Futures和flatMap(再次使用伪代码):

Okay, well, I see well enough how to chain asynchronous calls in Combine, for example by using Futures and flatMap (pseudocode again):

let future1 = Future...
future1.map {
    // do something
}.flatMap {
    let future2 = Future...
    return future2.map {
        // do something
    }
}
// ...

在该代码中,形成future2的方式可以取决于我们从future1的执行中收到的值,并且在future2map中,我们可以修改从上游接收到的值通过管道.没问题.一切都很漂亮.

In that code, the way we form future2 can depend upon the value we received from the execution of future1, and in the map on future2 we can modify what we received from upstream before it gets passed on down the pipeline. No problem. It's all quite beautiful.

但是,这并没有告诉我我在合并前代码(即循环)中所做的事情.在这里,我是在循环中进行多个异步调用,然后由DispatchGroup保留这些位置,然后继续操作.问题是:

But that doesn't give me what I was doing in the pre-Combine code, namely the loop. Here I was, doing multiple asynchronous calls in a loop, held in place by a DispatchGroup before proceeding. The question is:

执行此操作的组合模式是什么?

记住情况.我有一些对象的 array .我想通过该数组进行循环,在循环中对每个对象进行异步调用,异步获取新信息并在此基础上修改该对象,然后继续进行操作.管道.而且每个循环可能还涉及进一步的 nested 循环,甚至异步地收集更多信息:

Remember the situation. I've got an array of some object. I want to loop through that array, doing an asynchronous call for each object in the loop, fetching new info asynchronously and modifying that object on that basis, before proceeding on down the pipeline. And each loop might involve a further nested loop gathering even more information asynchronously:

Fetch info from online database, it's an array
   |
   V
For each element in the array, fetch _more_ info, _that's_ an array
   |
   V
For each element in _that_ array, fetch _more_ info
   |
   V
Loop thru the accumulated info and populate that element of the original array 

执行此操作的旧代码看起来很恐怖,到处都是嵌套的完成处理程序和DispatchGroup enter/leave/notify所保持的循环. 但是有效.我无法使我的Combine代码以相同的方式工作.我该怎么做?基本上,我的管道输出是一个东西的数组,我觉得我需要将该数组拆分为单个元素,对每个元素进行异步的操作,然后将这些元素放回到一个数组中.怎么样?

The old code for doing this was horrible-looking, full of nested completion handlers and loops held in place by DispatchGroup enter/leave/notify. But it worked. I can't get my Combine code to work the same way. How do I do it? Basically my pipeline output is an array of something, I feel like I need to split up that array into individual elements, do something asynchronously to each element, and put the elements back together into an array. How?

我一直在解决此问题的方法有效,但无法扩展,特别是当异步调用需要到达流水线链中 back 几个步骤的信息时.我一直在做这样的事情(我从 https://stackoverflow.com/a/58708381/341994):

The way I've been solving this works, but doesn't scale, especially when an asynchronous call needs information that arrived several steps back in the pipeline chain. I've been doing something like this (I got this idea from https://stackoverflow.com/a/58708381/341994):

  1. 一组对象从上游到达.

  1. An array of objects arrives from upstream.

我将一个flatMapmap数组输入到一个发布者数组,每个发布者均以Future为首,该Future会获取与 one 对象有关的更多在线内容,然后是一个产生 modified 对象的管道.

I enter a flatMap and map the array to an array of publishers, each headed by a Future that fetches further online stuff related to one object, and followed by a pipeline that produces the modified object.

现在我有一个管道数组,每个管道产生一个对象.我merge那个数组,并从flatMap产生那个发布者(一个MergeMany).

Now I have an array of pipelines, each producing a single object. I merge that array and produce that publisher (a MergeMany) from the flatMap.

I collect将结果值返回到数组中.

I collect the resulting values back into an array.

但是,这似乎仍然需要大量工作,而且更糟的是,当每个子管道本身需要产生一系列子管道时,它无法缩放.一切都变得难以理解,过去曾经很容易到达完成块的信息(由于Swift的作用域规则)不再进入主管道的后续步骤(或者因为我将越来越大的元组沿着管道向下传递而很难到达) ).

But this still seems like a lot of work, and even worse, it doesn't scale when each sub-pipeline itself needs to spawn an array of sub-pipelines. It all becomes incomprehensible, and information that used to arrive easily into a completion block (because of Swift's scoping rules) no longer arrives into a subsequent step in the main pipeline (or arrives only with difficulty because I pass bigger and bigger tuples down the pipeline).

必须有一些简单的合并"模式来执行此操作,但是我完全错过了它.请告诉我这是什么.

There must be some simple Combine pattern for doing this, but I'm completely missing it. Please tell me what it is.

推荐答案

最新修改和下面的注释:

With your latest edit and this comment below:

我实际上是在问是否有相当于在该步骤涉及多个异步步骤完成之后才继续进行下一步"的组合吗?

I literally am asking is there a Combine equivalent of "don't proceed to the next step until this step, involving multiple asynchronous steps, has finished"

我认为可以通过.flatMap实现此模式,方法是对数组发布器(Publishers.Sequence)进行逐一发出并完成,然后进行任何需要的逐元素异步处理,并以.collect,等待所有元素完成后再继续

I think this pattern can be achieved with .flatMap to an array publisher (Publishers.Sequence), which emits one-by-one and completes, followed by whatever per-element async processing is needed, and finalized with a .collect, which waits for all elements to complete before proceeding

因此,在代码中,假设我们具有以下功能:

So, in code, assuming we have these functions:

func getFoos() -> AnyPublisher<[Foo], Error>
func getPartials(for: Foo) -> AnyPublisher<[Partial], Error>
func getMoreInfo(for: Partial, of: Foo) -> AnyPublisher<MoreInfo, Error>

我们可以执行以下操作:

We can do the following:

getFoos()
.flatMap { fooArr in 
    fooArr.publisher.setFailureType(to: Error.self)
 }

// per-foo element async processing
.flatMap { foo in

  getPartials(for: foo)
    .flatMap { partialArr in
       partialArr.publisher.setFailureType(to: Error.self)
     }

     // per-partial of foo async processing
    .flatMap { partial in

       getMoreInfo(for: partial, of: foo)
         // build completed partial with more info
         .map { moreInfo in
            var newPartial = partial
            newPartial.moreInfo = moreInfo
            return newPartial
         }
     }
     .collect()
     // build completed foo with all partials
     .map { partialArr in
        var newFoo = foo
        newFoo.partials = partialArr
        return newFoo
     }
}
.collect()

(删除了原来的答案)

这篇关于合并框架:如何在继续进行之前异步处理数组的每个元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆