Позвольте мне сначала отметить, что я думаю, что вы отправляете на main
в начале вашего конвейера. Насколько я могу судить, все ваши map
преобразования являются чистыми функциями (без побочных эффектов или ссылок на изменяемое состояние), поэтому они также могут запускаться в фоновом потоке и, следовательно, не блокировать пользовательский интерфейс.
Во-вторых, как сказал Мэтт, Publisher
обычно можно использовать повторно. Ваш конвейер создает большой комплекс Publisher
, а затем подписывается на него, что приводит к AnyCancellable
. Поэтому выделите большой комплекс Publisher
, но не подписку.
Вы можете выделить его в метод расширения для вашего RestClient
для удобства:
extension RestClient {
func records<Record>(
forQuery query: String,
makeRecord: @escaping ([String: Any]) throws -> Record)
-> AnyPublisher<[Record], Never>
{
let request = self.request(forQuery: query, apiVersion: RestClient.apiVersion)
return self.publisher(for: request)
.tryMap { try $0.asJson() as? [String: Any] ?? [:] }
.map { $0["records"] as? [[String: Any]] ?? [] }
.tryMap { try $0.map { try makeRecord($0) } }
.mapError { dump($0) } // dump is a Swift standard function
.replaceError(with: []) // simpler than .catch
.eraseToAnyPublisher()
}
}
Тогда вы можете использовать это выглядит так:
struct Claim {
var id: String
var subject: String
var caseNumber: String
}
extension Claim {
static func from(json: [String: Any]) -> Claim {
return .init(
id: json["Id"] as? String ?? "None Listed",
subject: json["Subject"] as? String ?? "None Listed",
caseNumber: json["CaseNumber"] as? String ?? "0")
}
}
class MyController {
var claims: [Claim] = []
var caseCancellable: AnyCancellable?
func run() {
let existingClaimQuery = "SELECT Id, Subject, CaseNumber FROM Case WHERE Status != 'Closed' ORDER BY CaseNumber DESC"
caseCancellable = RestClient.shared.records(forQuery: existingClaimQuery, makeRecord: Claim.from(json:))
.receive(on: RunLoop.main)
.assign(to: \.claims, on: self)
}
}
Обратите внимание, что я добавил оператор receive(on: RunLoop.main)
в метод, который подписывается на издателя, а не встраивает его в издателя. Это позволяет легко добавлять дополнительные операторы, которые выполняются в фоновом планировщике, перед отправкой в основной поток.
ОБНОВЛЕНИЕ
Из вашего комментария:
В синтаксисе обещания я мог бы сказать execute run (), как определено выше, и .then (doSomethingWithThatData ()), зная, что doSomethingWithThatData не запустится, пока начальная работа не завершится успешно. Я пытаюсь разработать настройку, в которой мне нужно использовать этот метод записей (fromQuery :), а затем (и только потом) делать что-то с этими данными. Я изо всех сил пытаюсь понять, как это сделать до конца.
Я не знаю, какую реализацию обещаний вы используете, поэтому трудно понять, что делает ваш .then(doSomethingWithThatData())
. То, что вы написали, не имеет большого смысла в Swift. Возможно, вы имели в виду:
.then { data in doSomething(with: data) }
В этом случае метод doSomething(with:)
не может быть вызван до тех пор, пока не станет доступен data
, поскольку doSomething(with:)
принимает data
в качестве аргумента!