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