Ios 组合框架序列化异步操作

Ios 组合框架序列化异步操作,ios,swift,combine,Ios,Swift,Combine,如何使构成联合框架的异步管道同步(串行)排列 假设我有50个URL,我想从中下载相应的资源,假设我想一次下载一个。我知道如何使用Operation/OperationQueue实现这一点,例如,使用在下载完成之前不会声明自己已完成的Operation子类。我如何使用联合收割机做同样的事情 现在,我想到的就是保留一个剩余URL的全局列表,然后弹出一个,为一次下载设置一个管道,进行下载,然后在管道的接收器中重复。那看起来不太像 我确实尝试过制作一个URL数组,并将其映射到一个发布者数组。我知道我可以

如何使构成联合框架的异步管道同步(串行)排列

假设我有50个URL,我想从中下载相应的资源,假设我想一次下载一个。我知道如何使用Operation/OperationQueue实现这一点,例如,使用在下载完成之前不会声明自己已完成的Operation子类。我如何使用联合收割机做同样的事情

现在,我想到的就是保留一个剩余URL的全局列表,然后弹出一个,为一次下载设置一个管道,进行下载,然后在管道的
接收器中重复。那看起来不太像

我确实尝试过制作一个URL数组,并将其映射到一个发布者数组。我知道我可以使用
flatMap
来“生成”一个发布者并使其在管道上发布。但我还是同时下载。没有任何方法可以以可控的方式遍历阵列,或者说有吗


(我还想象着用未来做些什么,但我变得无可救药地困惑了。我不习惯这种思维方式。)

在所有其他反应式框架中,这真的很容易;您只需使用
concat
一步串联并展平结果,然后就可以
将结果缩减为最终数组。苹果使这一点变得困难,因为
Publisher.Concatenate
没有接受发布者数组的重载。
Publisher.Merge也有类似的奇怪之处。我有一种感觉,这与他们返回嵌套的泛型发布者,而不是像rx Observable那样只返回单个泛型类型有关。我想您可以只调用一个循环,然后将连接的结果减少到单个数组中,但我真的希望它们在下一版本中解决这个问题。当然有必要合并2个以上的发布者,合并4个以上的发布者(这两个运营商的重载甚至不一致,这很奇怪)

编辑:

回到这里,我发现您确实可以捕获任意一组发布者,它们将按顺序发出。我不知道为什么没有像
ConcatenateMany
这样的函数来为您执行此操作,但看起来只要您愿意使用类型擦除的发布服务器,自己编写一个并不是那么难。此示例显示merge按时间顺序发射,而concat按组合顺序发射:

import PlaygroundSupport
import SwiftUI
import Combine

let p = Just<Int>(1).append(2).append(3).delay(for: .seconds(0.25), scheduler: RunLoop.main).eraseToAnyPublisher()
let q = Just<Int>(4).append(5).append(6).eraseToAnyPublisher()
let r = Just<Int>(7).append(8).append(9).delay(for: .seconds(0.5), scheduler: RunLoop.main).eraseToAnyPublisher()
let concatenated: AnyPublisher<Int, Never> = [q,r].reduce(p) { total, next in
  total.append(next).eraseToAnyPublisher()
}

var subscriptions = Set<AnyCancellable>()

concatenated
  .sink(receiveValue: { v in
    print("concatenated: \(v)")
  }).store(in: &subscriptions)

Publishers
  .MergeMany([p,q,r])
  .sink(receiveValue: { v in
    print("merge: \(v)")
  }).store(in: &subscriptions)
导入PlaygroundSupport
导入快捷键
进口联合收割机
设p=Just(1).append(2).append(3).delay(时间:。秒(0.25),调度程序:RunLoop.main.橡皮擦到任何发布程序()
设q=Just(4).append(5).append(6).eraseToAnyPublisher()
设r=Just(7).append(8).append(9).delay(时间:。秒(0.5),调度程序:RunLoop.main.橡皮擦AnyPublisher()
让连接:AnyPublisher=[q,r].reduce(p){total,下一个在
total.append(下一个).eraseToAnyPublisher()
}
var subscriptions=Set()
串联
.sink(接收值:{v in
打印(“连接:\(v)”)
}).store(位于:&订阅中)
出版者
.MergeMany([p,q,r])
.sink(接收值:{v in
打印(“合并:\(v)”)
}).store(位于:&订阅中)

这是一页描述可能方法的操场代码。其主要思想是将异步API调用转换为
未来的
发布链,从而实现串行管道

输入:int的范围从1到10,在后台队列上异步转换为字符串

直接调用异步API的演示:

let group = DispatchGroup()
inputValues.map {
    group.enter()
    asyncCall(input: $0) { (output, _) in
        print(">> \(output), in \(Thread.current)")
        group.leave()
    }
}
group.wait()
输出:

代码:

导入可可粉
进口联合收割机
导入PlaygroundSupport
//假设存在一些具有
//(例如,在一段时间内处理Int输入值并生成字符串结果)
func异步调用(输入:Int,完成:@escaping(字符串,错误?)->Void){
DispatchQueue.global(qos:.background).async{
sleep(.random(in:1…5))//等待随机异步API输出
完成(“\(输入)”,无)
}
}
//有一些输入值需要串行处理
让inputValues=数组(1…10)
//基于Future准备一个管道项目,该项目为Transform Async->Sync
func makeFuture(输入:Int)->AnyPublisher{
未来{
中的异步调用(输入:输入){(值,错误)
如果let error=error{
承诺(失败(错误))
}否则{
承诺(.成功(价值))
}
}
}
.receive(在:DispatchQueue.main上)
.地图{

print(“>>得到\($0)”)/我只是简单地测试了一下,但在第一次通过时,似乎每个请求都会等待前一个请求完成,然后再开始

我发布此解决方案是为了寻求反馈。如果这不是一个好的解决方案,请批评

扩展集合,其中元素:Publisher{
func serialize()->AnyPublisher{
//如果集合为空,我们不能创建一个任意发布者
//因此,我们返回nil,表示没有要序列化的内容。
如果为空{返回nil}
//我们知道在这一点上,抓住第一个出版商是安全的。
让第一=自我第一!
//如果只有一个出版商,那么我们可以把它退回。
如果计数=1{首先返回.橡皮擦到任何发布者()
//我们将从第一个发布者开始建立输出。
var output=first.eraseToAnyPublisher()
//我们迭代其余的发布者(跳过第一个)
对于self.dropFirst()中的publisher{
//我们通过添加下一个发布服务器来建立输出。
output=output.append(publisher.eraseToAnyPublisher())
}
返回输出
}
}

此解决方案的更简洁版本(由@matt提供):

扩展集合,其中元素:Publi
>> 1, in <NSThread: 0x7fe76264fff0>{number = 4, name = (null)}
>> 3, in <NSThread: 0x7fe762446b90>{number = 3, name = (null)}
>> 5, in <NSThread: 0x7fe7624461f0>{number = 5, name = (null)}
>> 6, in <NSThread: 0x7fe762461ce0>{number = 6, name = (null)}
>> 10, in <NSThread: 0x7fe76246a7b0>{number = 7, name = (null)}
>> 4, in <NSThread: 0x7fe764c37d30>{number = 8, name = (null)}
>> 7, in <NSThread: 0x7fe764c37cb0>{number = 9, name = (null)}
>> 8, in <NSThread: 0x7fe76246b540>{number = 10, name = (null)}
>> 9, in <NSThread: 0x7fe7625164b0>{number = 11, name = (null)}
>> 2, in <NSThread: 0x7fe764c37f50>{number = 12, name = (null)}
>> got 1
>> got 2
>> got 3
>> got 4
>> got 5
>> got 6
>> got 7
>> got 8
>> got 9
>> got 10
>>>> finished with true
import Cocoa
import Combine
import PlaygroundSupport

// Assuming there is some Asynchronous API with
// (eg. process Int input value during some time and generates String result)
func asyncCall(input: Int, completion: @escaping (String, Error?) -> Void) {
    DispatchQueue.global(qos: .background).async {
            sleep(.random(in: 1...5)) // wait for random Async API output
            completion("\(input)", nil)
        }
}

// There are some input values to be processed serially
let inputValues = Array(1...10)

// Prepare one pipeline item based on Future, which trasform Async -> Sync
func makeFuture(input: Int) -> AnyPublisher<Bool, Error> {
    Future<String, Error> { promise in
        asyncCall(input: input) { (value, error) in
            if let error = error {
                promise(.failure(error))
            } else {
                promise(.success(value))
            }
        }
    }
    .receive(on: DispatchQueue.main)
    .map {
        print(">> got \($0)") // << sideeffect of pipeline item
        return true
    }
    .eraseToAnyPublisher()
}

// Create pipeline trasnforming input values into chain of Future publishers
var subscribers = Set<AnyCancellable>()
let pipeline =
    inputValues
    .reduce(nil as AnyPublisher<Bool, Error>?) { (chain, value) in
        if let chain = chain {
            return chain.flatMap { _ in
                makeFuture(input: value)
            }.eraseToAnyPublisher()
        } else {
            return makeFuture(input: value)
        }
    }

// Execute pipeline
pipeline?
    .sink(receiveCompletion: { _ in
        // << do something on completion if needed
    }) { output in
        print(">>>> finished with \(output)")
    }
    .store(in: &subscribers)

PlaygroundPage.current.needsIndefiniteExecution = true
import PlaygroundSupport
import SwiftUI
import Combine

class MySubscriber: Subscriber {
  typealias Input = String
  typealias Failure = Never

  func receive(subscription: Subscription) {
    print("Received subscription", Thread.current.isMainThread)
    subscription.request(.max(1))
  }

  func receive(_ input: Input) -> Subscribers.Demand {
    print("Received input: \(input)", Thread.current.isMainThread)
    return .max(1)
  }

  func receive(completion: Subscribers.Completion<Never>) {
    DispatchQueue.main.async {
        print("Received completion: \(completion)", Thread.current.isMainThread)
        PlaygroundPage.current.finishExecution()
    }
  }
}

(110...120)
    .publisher.receive(on: DispatchQueue.global())
    .map {
        print(Thread.current.isMainThread, Thread.current)
        usleep(UInt32.random(in: 10000 ... 1000000))
        return String(format: "%02x", $0)
    }
    .subscribe(on: DispatchQueue.main)
    .subscribe(MySubscriber())

print("Hello")

PlaygroundPage.current.needsIndefiniteExecution = true
Hello
Received subscription true
false <NSThread: 0x600000064780>{number = 5, name = (null)}
Received input: 6e false
false <NSThread: 0x60000007cc80>{number = 9, name = (null)}
Received input: 6f false
false <NSThread: 0x60000007cc80>{number = 9, name = (null)}
Received input: 70 false
false <NSThread: 0x60000007cc80>{number = 9, name = (null)}
Received input: 71 false
false <NSThread: 0x60000007cc80>{number = 9, name = (null)}
Received input: 72 false
false <NSThread: 0x600000064780>{number = 5, name = (null)}
Received input: 73 false
false <NSThread: 0x600000064780>{number = 5, name = (null)}
Received input: 74 false
false <NSThread: 0x60000004dc80>{number = 8, name = (null)}
Received input: 75 false
false <NSThread: 0x60000004dc80>{number = 8, name = (null)}
Received input: 76 false
false <NSThread: 0x60000004dc80>{number = 8, name = (null)}
Received input: 77 false
false <NSThread: 0x600000053400>{number = 3, name = (null)}
Received input: 78 false
Received completion: finished true
import PlaygroundSupport
import Combine
import Foundation

PlaygroundPage.current.needsIndefiniteExecution = true

let A = (1 ... 9)
    .publisher
    .flatMap(maxPublishers: .max(1)) { value in
        [value].publisher
            .flatMap { value in
                Just(value)
                    .delay(for: .milliseconds(Int.random(in: 0 ... 100)), scheduler: DispatchQueue.global())
        }
}
.sink { value in
    print(value, "A")
}

let B = (1 ... 9)
    .publisher
    .flatMap { value in
        [value].publisher
            .flatMap { value in
                Just(value)
                    .delay(for: .milliseconds(Int.random(in: 0 ... 100)), scheduler: RunLoop.main)
        }
}
.sink { value in
    print("     ",value, "B")
}
1 A
      4 B
      5 B
      7 B
      1 B
      2 B
      8 B
      6 B
2 A
      3 B
      9 B
3 A
4 A
5 A
6 A
7 A
8 A
9 A
import PlaygroundSupport
import Foundation
import Combine

let path = "postman-echo.com/get"
let urls: [URL] = "... which proves the downloads are happening serially .-)".map(String.init).compactMap { (parameter) in
    var components = URLComponents()
    components.scheme = "https"
    components.path = path
    components.queryItems = [URLQueryItem(name: parameter, value: nil)]
    return components.url
}
//["https://postman-echo.com/get?]
struct Postman: Decodable {
    var args: [String: String]
}


let collection = urls.compactMap { value in
        URLSession.shared.dataTaskPublisher(for: value)
        .tryMap { data, response -> Data in
            return data
        }
        .decode(type: Postman.self, decoder: JSONDecoder())
        .catch {_ in
            Just(Postman(args: [:]))
    }
}

extension Collection where Element: Publisher {
    func serialize() -> AnyPublisher<Element.Output, Element.Failure>? {
        guard let start = self.first else { return nil }
        return self.dropFirst().reduce(start.eraseToAnyPublisher()) {
            return $0.append($1).eraseToAnyPublisher()
        }
    }
}

var streamA = ""
let A = collection
    .publisher.flatMap{$0}

    .sink(receiveCompletion: { (c) in
        print(streamA, "     ", c, "    .publisher.flatMap{$0}")
    }, receiveValue: { (postman) in
        print(postman.args.keys.joined(), terminator: "", to: &streamA)
    })


var streamC = ""
let C = collection
    .serialize()?

    .sink(receiveCompletion: { (c) in
        print(streamC, "     ", c, "    .serialize()?")
    }, receiveValue: { (postman) in
        print(postman.args.keys.joined(), terminator: "", to: &streamC)
    })

var streamD = ""
let D = collection
    .publisher.flatMap(maxPublishers: .max(1)){$0}

    .sink(receiveCompletion: { (c) in
        print(streamD, "     ", c, "    .publisher.flatMap(maxPublishers: .max(1)){$0}")
    }, receiveValue: { (postman) in
        print(postman.args.keys.joined(), terminator: "", to: &streamD)
    })

PlaygroundPage.current.needsIndefiniteExecution = true
.w.h i.c hporves ht edownloadsa erh appeninsg eriall y.-)       finished     .publisher.flatMap{$0}
... which proves the downloads are happening serially .-)       finished     .publisher.flatMap(maxPublishers: .max(1)){$0}
... which proves the downloads are happening serially .-)       finished     .serialize()?
import Combine

let sequencePublisher = Publishers.Sequence<Range<Int>, Never>(sequence: 0..<Int.max)
let subject = PassthroughSubject<String, Never>()

let handle = subject
    .zip(sequencePublisher.print())
    //.publish
    .flatMap(maxPublishers: .max(1), { (pair)  in
        Just(pair)
    })
    .print()
    .sink { letters, digits in
        print(letters, digits)
    }

"Hello World!".map(String.init).forEach { (s) in
    subject.send(s)
}
subject.send(completion: .finished)
let collection = (1 ... 10).map {
    Just($0).delay(
        for: .seconds(Double.random(in:1...5)),
        scheduler: DispatchQueue.main)
        .eraseToAnyPublisher()
}
collection.publisher
    .flatMap() {$0}
    .sink {print($0)}.store(in:&self.storage)
.flatMap {$0}
let collection = (1 ... 10).map {
    Just($0).delay(
        for: .seconds(Double.random(in:1...5)),
        scheduler: DispatchQueue.main)
        .eraseToAnyPublisher()
}
collection.publisher
    .flatMap(maxPublishers:.max(1)) {$0}
    .sink {print($0)}.store(in:&self.storage)
let eph = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: eph)
let url = "https://photojournal.jpl.nasa.gov/tiff/PIA23172.tif"
let collection = [url, url, url]
    .map {URL(string:$0)!}
    .map {session.dataTaskPublisher(for: $0)
        .eraseToAnyPublisher()
}
collection.publisher.setFailureType(to: URLError.self)
    .handleEvents(receiveOutput: {_ in print("start")})
    .flatMap() {$0}
    .map {$0.data}
    .sink(receiveCompletion: {comp in
        switch comp {
        case .failure(let err): print("error", err)
        case .finished: print("finished")
        }
    }, receiveValue: {_ in print("done")})
    .store(in:&self.storage)
start
start
start
done
done
done
finished
    .flatMap() {$0}
    .flatMap(maxPublishers:.max(1) {$0}
start
done
start
done
start
done
finished
let collection = (1 ... 10).map {
    Just($0).delay(
        for: .seconds(Double.random(in:1...5)),
        scheduler: DispatchQueue.main)
        .eraseToAnyPublisher()
}
let pub = collection.dropFirst().reduce(collection.first!) {
    return $0.append($1).eraseToAnyPublisher()
}
pub.sink {print($0)}.store(in:&self.storage)
extension Collection where Element: Publisher {
    func serialize() -> AnyPublisher<Element.Output, Element.Failure>? {
        guard let start = self.first else { return nil }
        return self.dropFirst().reduce(start.eraseToAnyPublisher()) {
            return $0.append($1).eraseToAnyPublisher()
        }
    }
}
func imagesPublisher(for urls: [URL]) -> AnyPublisher<UIImage, URLError> {
    Publishers.Sequence(sequence: urls.map { self.imagePublisher(for: $0) })
        .flatMap(maxPublishers: .max(1)) { $0 }
        .eraseToAnyPublisher()
}
func imagePublisher(for url: URL) -> AnyPublisher<UIImage, URLError> {
    URLSession.shared.dataTaskPublisher(for: url)
        .compactMap { UIImage(data: $0.data) }
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}
var imageRequests: AnyCancellable?

func fetchImages() {
    imageRequests = imagesPublisher(for: urls).sink { completion in
        switch completion {
        case .finished:
            print("done")
        case .failure(let error):
            print("failed", error)
        }
    } receiveValue: { image in
        // do whatever you want with the images as they come in
    }
}
      var array: [AnyPublisher<Data, URLError>] = []

      array.append(Task())

      array.publisher
         .flatMap { $0 }
         .sink {

         }
         // it will be finished
      array.append(Task())
      array.append(Task())
      array.append(Task())