Kết hợp khung nối tiếp các hoạt động async


8

Làm cách nào để tôi có được các đường ống không đồng bộ cấu thành khung Kết hợp để xếp hàng một cách đồng bộ (theo kiểu huyết thanh)?

Giả sử tôi có 50 URL mà tôi muốn tải xuống các tài nguyên tương ứng và giả sử tôi muốn làm điều đó một lần. Tôi biết cách thực hiện điều đó với Thao tác / OperationQueue, ví dụ: sử dụng lớp con Hoạt động không tự khai báo cho đến khi quá trình tải xuống hoàn tất. Làm thế nào tôi có thể làm điều tương tự bằng cách sử dụng Kết hợp?

Hiện tại, tất cả những gì xảy ra với tôi là giữ một danh sách toàn cầu các URL còn lại và bật một URL, thiết lập một đường ống cho một lần tải xuống, thực hiện tải xuống và trong sinkđường ống, lặp lại. Điều đó dường như không giống như Kết hợp.

Tôi đã thử tạo một mảng các URL và ánh xạ nó tới một loạt các nhà xuất bản. Tôi biết tôi có thể "sản xuất" một nhà xuất bản và khiến nó xuất bản trên đường ống bằng cách sử dụng flatMap. Nhưng sau đó tôi vẫn đang thực hiện tải xuống đồng thời. Không có cách kết hợp nào để đi theo mảng một cách có kiểm soát - hay là ở đó?

(Tôi cũng tưởng tượng làm gì đó với Tương lai nhưng tôi trở nên bối rối vô vọng. Tôi không quen với cách nghĩ này.)

Câu trả lời:


2

Tôi chỉ thử nghiệm ngắn gọn về điều này, nhưng ở lần đầu tiên, có vẻ như mỗi yêu cầu đợi yêu cầu trước đó kết thúc trước khi bắt đầu.

Tôi đang đăng giải pháp này để tìm kiếm thông tin phản hồi. Xin hãy nghiêm túc nếu đây không phải là một giải pháp tốt.

extension Collection where Element: Publisher {

    func serialize() -> AnyPublisher<Element.Output, Element.Failure>? {
        // If the collection is empty, we can't just create an arbititary publisher
        // so we return nil to indicate that we had nothing to serialize.
        if isEmpty { return nil }

        // We know at this point that it's safe to grab the first publisher.
        let first = self.first!

        // If there was only a single publisher then we can just return it.
        if count == 1 { return first.eraseToAnyPublisher() }

        // We're going to build up the output starting with the first publisher.
        var output = first.eraseToAnyPublisher()

        // We iterate over the rest of the publishers (skipping over the first.)
        for publisher in self.dropFirst() {
            // We build up the output by appending the next publisher.
            output = output.append(publisher).eraseToAnyPublisher()
        }

        return output
    }
}


Một phiên bản ngắn gọn hơn của giải pháp này (được cung cấp bởi @matt):

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()) {
            $0.append($1).eraseToAnyPublisher()
        }
    }
}

Cảm ơn vô cùng. appendchính xác là những gì tôi đang tìm kiếm. - Mã của bạn có thể được thắt chặt đáng kể; đặc biệt, không cần phải trả lại sớm trong trường hợp đó count == 1, vì trong trường hợp đó dropFirstsẽ trống và chúng tôi sẽ không lặp lại. Và không cần phải duy trì outputbiến, bởi vì chúng ta có thể sử dụng reducethay vì for...in. Xem câu trả lời của tôi để hiển thị chặt chẽ hơn.
matt

3

Bạn có thể tạo Người đăng ký tùy chỉnh nơi nhận được Người đăng ký trở lại.Demand.max (1). Trong trường hợp đó, thuê bao sẽ chỉ yêu cầu giá trị tiếp theo khi nhận được một. Ví dụ này dành cho Int.publisher, nhưng một số độ trễ ngẫu nhiên trong lưu lượng truy cập mạng bắt chước :-)

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

Sân chơi in ...

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

CẬP NHẬT cuối cùng tôi đã tìm thấy .flatMap(maxPublishers: ), điều đó buộc tôi phải cập nhật chủ đề thú vị này với cách tiếp cận khác nhau một chút. Xin vui lòng, hãy xem rằng tôi đang sử dụng hàng đợi toàn cầu để lập lịch, không chỉ một số độ trễ ngẫu nhiên, chỉ để chắc chắn rằng việc nhận luồng nối tiếp không phải là hành vi "ngẫu nhiên" hay "may mắn" :-)

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")
}

in

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

Dựa trên văn bản ở đây

.serialize ()?

được xác định bởi Clay Ellis câu trả lời được chấp nhận có thể được thay thế bằng

.publisher.flatMap (maxPublishers: .max (1)) {$ 0}

trong khi phiên bản "chưa được phân loại" phải sử dụng

.publisher.flatMap {$ 0}

"ví dụ thế giới thực"

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

in

.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()?

Dường như với tôi rất hữu ích trong các kịch bản khác. Hãy thử sử dụng giá trị mặc định của maxPublishers trong đoạn mã tiếp theo và so sánh kết quả :-)

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)

@matt chìm không hoạt động khác nhau, chỉ khi nhận được Người đăng ký trả lại.Demand.unlrict ... Có thể sử dụng công cụ thích hợp, như hàng đợi nối tiếp và Data.init? (urlOf: URL) là tùy chọn tốt nhất trong kịch bản của bạn . Nếu bạn cần tổng hai số Int, bạn có làm như [lhs: Int, rhs: Int] .reduce .... ??? Tôi sẽ sử dụng Data.init? (Nội dung url: URL) bên trong nhận (_ input :) của MySerialDoaderSubscacker.
dùng3441734

@matt xin vui lòng, xem câu trả lời cập nhật. Kết hợp rất thú vị, nhưng (ít nhất là đối với tôi) rất khó hiểu ...
user3441734

Có, tôi thấy! Với maxPublisherstham số, chúng ta có thể thêm áp suất ngược. Điều này đúng với những gì tôi đã nói trong câu hỏi của mình: "Tôi biết tôi có thể" sản xuất "một nhà xuất bản và khiến nó xuất bản trên đường ống bằng cách sử dụng FlatMap. Nhưng sau đó tôi vẫn thực hiện tải xuống đồng thời." Vâng, với maxPublisherstham số, chúng không đồng thời.
mờ

@matt có, thuê bao riêng của nhà xuất bản cuộc gọi với Người đăng ký.Demand.unliated, FlatMap có tác dụng tương tự như thuê bao riêng của nhà xuất bản với giá trị khác nhau, trong trường hợp sử dụng của chúng tôi .max (1). Tôi chỉ thêm một ví dụ khác với kịch bản khác, nơi nó có thể sử dụng được.
dùng3441734

2

Trong tất cả các khung Reactive khác, điều này thực sự dễ dàng; bạn chỉ cần sử dụng concatđể nối và làm phẳng các kết quả trong một bước và sau đó bạn có thể reducekết quả thành một mảng cuối cùng. Apple làm cho điều này trở nên khó khăn vì Publisher.Concatenatekhông có tình trạng quá tải chấp nhận một loạt các Nhà xuất bản. Có sự kỳ lạ tương tự với Publisher.Merge. Tôi có cảm giác điều này có liên quan đến thực tế là họ trả lại các nhà xuất bản chung lồng nhau thay vì chỉ trả lại một loại chung chung như rx Observable. Tôi đoán bạn chỉ có thể gọi Concatenatetrong một vòng lặp và sau đó giảm các kết quả được nối vào một mảng duy nhất, nhưng tôi thực sự hy vọng họ giải quyết vấn đề này trong phiên bản tiếp theo. Chắc chắn cần phải có nhiều hơn 2 nhà xuất bản và hợp nhất hơn 4 nhà xuất bản (và tình trạng quá tải cho hai nhà khai thác này thậm chí không nhất quán, điều này thật kỳ lạ).

BIÊN TẬP:

Tôi đã quay lại vấn đề này và thấy rằng bạn thực sự có thể kết hợp một loạt các nhà xuất bản tùy ý và họ sẽ phát ra theo thứ tự. Tôi không biết tại sao không có chức năng nào thích ConcatenateManylàm điều này cho bạn nhưng có vẻ như miễn là bạn sẵn sàng sử dụng một nhà xuất bản bị xóa, không khó để tự viết một cái. Ví dụ này cho thấy hợp nhất phát ra theo thứ tự thời gian trong khi concat phát theo thứ tự kết hợp:

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)

Vâng, bạn có thể đoán tôi đã chọn một con số lớn như 50 cố ý.
matt

Có một MergeMany. Tôi không hiểu tại sao không có ConcatenateMany. Rx swift có Observable.concat và Reactive Swift có FlatMap (.concat) nên điều này thật lạ; có lẽ tôi đang thiếu một cái gì đó Tôi sẽ tiếp tục tìm kiếm developer.apple.com/documentation/combine/publishers/mergemany
Josh Homann

Sẽ concattuần tự hóa (trong các khung phản ứng khác)?
matt

Đúng. Đối với Chuỗi trình tự, bạn chỉ có một cách làm phẳng tức là đặt các phần tử của một chuỗi bên trong sau một chuỗi khác giống như Sequence.flatMap trong swift. Khi bạn có một chuỗi không đồng bộ, bạn phải xem xét kích thước thời gian khi làm phẳng. Vì vậy, bạn có thể phát ra các phần tử từ tất cả các chuỗi bên trong theo thứ tự thời gian (hợp nhất) hoặc bạn có thể phát ra các phần tử từ mỗi chuỗi bên trong theo thứ tự các chuỗi (concat). Xem sơ đồ đá cẩm thạch: rxmarble.com/#concat vs rxmarbled.com/#merge
Josh Homann

Lưu ý rằng đó .appendlà một toán tử tạo ra một Publisher.Concatenate.
cướp

2

Từ câu hỏi ban đầu:

Tôi đã thử tạo một mảng các URL và ánh xạ nó tới một loạt các nhà xuất bản. Tôi biết tôi có thể "sản xuất" một nhà xuất bản và khiến nó xuất bản trên đường ống bằng cách sử dụng flatMap. Nhưng sau đó tôi vẫn đang thực hiện tải xuống đồng thời. Không có cách kết hợp nào để đi theo mảng một cách có kiểm soát - hay là ở đó?


Đây là một ví dụ đồ chơi để giải quyết vấn đề thực sự:

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)

Điều này phát ra các số nguyên từ 1 đến 10 theo thứ tự ngẫu nhiên đến vào thời điểm ngẫu nhiên. Mục tiêu là làm một cái gì đó với collectionđiều đó sẽ khiến nó phát ra các số nguyên từ 1 đến 10 theo thứ tự.


Bây giờ chúng ta sẽ thay đổi chỉ một điều: trong dòng

.flatMap {$0}

chúng tôi thêm maxPublisherstham số:

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)

Presto, bây giờ chúng ta làm phát ra các số nguyên từ 1 đến 10, theo thứ tự, với khoảng ngẫu nhiên giữa chúng.


Hãy áp dụng điều này cho vấn đề ban đầu. Để chứng minh, tôi cần một kết nối Internet khá chậm và tài nguyên khá lớn để tải xuống. Đầu tiên, tôi sẽ làm điều đó với bình thường .flatMap:

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)

Kết quả là

start
start
start
done
done
done
finished

Điều đó cho thấy rằng chúng tôi đang thực hiện ba lần tải xuống cùng một lúc. Được rồi, bây giờ thay đổi

    .flatMap() {$0}

đến

    .flatMap(maxPublishers:.max(1) {$0}

Kết quả bây giờ là:

start
done
start
done
start
done
finished

Vì vậy, chúng tôi hiện đang tải xuống ser seri, đó là vấn đề ban đầu được giải quyết.


chắp thêm

Theo nguyên tắc của TIMTOWTDI, thay vào đó chúng ta có thể xâu chuỗi các nhà xuất bản appendđể tuần tự hóa chúng:

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()
}

Kết quả là một nhà xuất bản nối tiếp các nhà xuất bản bị trì hoãn trong bộ sưu tập gốc. Hãy chứng minh bằng cách đăng ký nó:

pub.sink {print($0)}.store(in:&self.storage)

Chắc chắn, các số nguyên bây giờ đến theo thứ tự (với các khoảng ngẫu nhiên giữa).


Chúng tôi có thể gói gọn việc tạo ra pubtừ một bộ sưu tập các nhà xuất bản với phần mở rộng trên Bộ sưu tập, theo đề xuất của Clay Ellis:

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()
        }
    }
}

1

Đây là một mã sân chơi mô tả cách tiếp cận có thể. Ý tưởng chính là chuyển đổi các lệnh gọi API không đồng bộ thành chuỗi các Futurenhà xuất bản, do đó thực hiện đường ống nối tiếp.

Đầu vào: phạm vi int từ 1 đến 10 mà không đồng nhất trên hàng đợi nền được chuyển đổi thành chuỗi

Bản demo của cuộc gọi trực tiếp tới API async:

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

Đầu ra:

>> 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)}

Bản demo của đường ống kết hợp:

Đầu ra:

>> got 1
>> got 2
>> got 3
>> got 4
>> got 5
>> got 6
>> got 7
>> got 8
>> got 9
>> got 10
>>>> finished with true

Mã số:

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

0

Sử dụng flatMap(maxPublishers:transform:)với .max(1), ví dụ

func imagesPublisher(for urls: [URL]) -> AnyPublisher<UIImage, URLError> {
    Publishers.Sequence(sequence: urls.map { self.imagePublisher(for: $0) })
        .flatMap(maxPublishers: .max(1)) { $0 }
        .eraseToAnyPublisher()
}

Ở đâu

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(receiveCompletion: { 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
    })
}

Điều đó dẫn đến:

nối tiếp

Nhưng chúng ta nên nhận ra rằng bạn đạt được thành tích lớn khi thực hiện chúng liên tục, như thế. Ví dụ: nếu tôi tăng gấp 6 lần một lần, thì nhanh hơn gấp đôi:

đồng thời

Cá nhân, tôi khuyên bạn chỉ nên tải xuống tuần tự nếu bạn hoàn toàn phải (mà khi tải xuống một loạt hình ảnh / tệp, gần như chắc chắn không phải vậy). Có, việc thực hiện đồng thời các yêu cầu có thể khiến chúng không hoàn thành theo một thứ tự cụ thể, nhưng chúng tôi chỉ sử dụng một cấu trúc độc lập với đơn hàng (ví dụ: từ điển chứ không phải là một mảng đơn giản), nhưng hiệu suất đạt được rất đáng kể.

Nhưng, nếu bạn muốn chúng được tải xuống tuần tự, maxPublisherstham số có thể đạt được điều đó.


Đúng, đó là những gì câu trả lời của tôi đã nói: stackoverflow.com/a/59889993/341994 cũng như câu trả lời tôi đã trao tiền thưởng cho stackoverflow.com/a/59889174/341994
matt

Và xem thêm bây giờ cuốn sách của tôi apeth.com/Under HiểuCombine / operators / từ
matt

Nhân tiện, nói về tuần tự, tôi đã sử dụng rất tốt Hoạt động không đồng bộ tuần tự của bạn cho một nhiệm vụ khác, cảm ơn vì đã viết nó
matt

@matt - Lol. Tôi thú nhận rằng tôi đã không thấy rằng bạn đã tìm thấy maxPublisherstùy chọn. Và tôi sẽ không bao giờ nói về việc không làm serial serial nếu tôi nhận thấy đó là bạn (vì tôi biết bạn hoàn toàn hiểu những ưu và nhược điểm của nối tiếp so với đồng thời). Tôi thực sự chỉ nhìn thấy tôi muốn tải xuống một tệp tại một thời điểm, tôi gần đây đã tình cờ tìm thấy maxPublisherstùy chọn cho một thứ khác mà tôi đang làm (cụ thể là cung cấp giải pháp hiện đại cho câu hỏi này ) và tôi nghĩ rằng tôi sẽ chia sẻ giải pháp Kết hợp tôi đã đưa ra. Tôi không có nghĩa là rất phái sinh.
Cướp

1
Vâng, đó là giải pháp được đề cập tại stackoverflow.com/a/48104095/1271826 mà tôi đã nói trước đây; Tôi thấy rằng rất hữu ích.
matt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.