Прежде всего, у вас есть несколько декодируемых типов, которые нам нужно смоделировать, чтобы поиграться:
struct ApiResponse: Decodable {
var body: String
}
// Does the abbreviation "Amzn" really improve the program?
struct AmazonResponse: Decodable {
var contents: [AmazonItem]
}
struct AmazonItem: Decodable {
var geoid: String
}
Затем у вас есть пара пользовательских Publisher
операторов, каждый из которых должен создать URLRequest
. Давайте уменьшим вложение и позволим Swift выводить больше типов, выделив этот код:
func apiRequest(for region: MKCoordinateRegion) -> URLRequest {
// Your code here. fatalError gets this through the compiler.
fatalError()
}
func geographiesRequest(forIds ids: [String]) -> URLRequest {
// Your code here. fatalError gets this through the compiler.
fatalError()
}
Теперь давайте рассмотрим ваш первый пользовательский оператор, toRegionDataTask
.
You определили его только для издателей, где Failure == URLError
. Может быть, это то, что вы действительно хотите, но, так как мы все равно будем декодировать нисходящий поток, а декодирование имеет тип Error
, то *1016*, давайте просто будем использовать Error
.
Вы должны были вручную указать тип Publisher
, возвращаемый преобразованием flatMap
. Поскольку мы выделили apiRequest(for:)
, нам больше не нужно это делать.
Поэтому мы можем попробовать это:
extension Publisher where Output == MKCoordinateRegion {
func toRegionDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
return self
.map { apiRequest(for: $0) }
.flatMap { URLSession.shared.dataTaskPublisher(for: $0) }
.eraseToAnyPublisher()
}
}
Но горе нам, Компилятор имеет жалобы:
ошибка: без названия Page.xcplaygroundpage: 31: 18: ошибка: метод экземпляра 'flatMap (maxPublishers: _ :)' требует типы 'Self.Failure' и 'URLSession.DataTaskPublisher .Failure '(он же' URLError ') должен быть эквивалентен
.flatMap { URLSession.shared.dataTaskPublisher(for: $0) }
^
error: без названия Page.xcplaygroundpage: 32: 18: error: невозможно преобразовать возвращаемое выражение типа' AnyPublisher '(aka' AnyPublisher <(data: Данные, ответ: URLResponse), Self.Failure> ') для возврата типа «AnyPublisher» (он же «AnyPublisher <(данные: Данные, ответ: URLResponse), Ошибка>»)
.eraseToAnyPublisher()
^
Страница без названия. xcplaygroundpage: 32: 18: примечание: ожидается, что аргументы универсального параметра 'Failure' ('Self.Failure' и 'Error') будут равны
.eraseToAnyPublisher()
^
Способ отладки это разбейте его на несколько шагов и используйте eraseToAnyPublisher
после каждого шага, чтобы увидеть Output
и Failure
ty pes:
func toRegionDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
let x = self
.map { apiRequest(for: $0) }
.eraseToAnyPublisher()
let y = x
.flatMap { URLSession.shared.dataTaskPublisher(for: $0) }
.eraseToAnyPublisher()
return y
}
Теперь мы можем видеть (щелкнув по опции x
), что после map
, Output
равно URLRequest
и Failure
равно Self.Failure
- независимо от типа ошибки self
производит. Это имеет смысл, потому что я удалил ограничение Failure == URLError
из расширения.
Теперь компилятор просто выдает первую из предыдущих жалоб:
Ошибка: Untitled Page.xcplaygroundpage: 34 : 18: ошибка: метод экземпляра «flatMap (maxPublishers: _ :)» требует, чтобы типы «Self.Failure» и «URLSession.DataTaskPublisher.Failure» (также известный как «URLError») были эквивалентны
Это говорит, что тип «input» Failure
для оператора flatMap
должен совпадать с типом «output» Failure
. Тип ввода Self.Failure
, а вывод URLError
. Вероятно, поэтому вы ограничили Failure == URLError
расширением. Но я предпочитаю решать это по-разному, конвертируя оба типа ошибок в Error
, используя mapError
. Это облегчает написание тестов для метода и изменение его использования в будущем. Вот что я хотел бы сделать:
extension Publisher where Output == MKCoordinateRegion {
func toRegionDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
let x = self
.map { apiRequest(for: $0) }
.mapError { $0 as Error }
// ^^^^^^^^^^^^^^^^^^^^^^^^^
.eraseToAnyPublisher()
let y = x
.flatMap { URLSession.shared.dataTaskPublisher(for: $0).mapError { $0 as Error } }
// ^^^^^^^^^^^^^^^^^^^^^^^^
.eraseToAnyPublisher()
return y
}
}
Наконец, мы можем удалить промежуточные шаги, чтобы получить окончательную версию:
extension Publisher where Output == MKCoordinateRegion {
func toRegionDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
return self
.map { apiRequest(for: $0) }
.mapError { $0 as Error }
.flatMap { URLSession.shared.dataTaskPublisher(for: $0).mapError { $0 as Error } }
.eraseToAnyPublisher()
}
}
Мы дадим toGeographiesDataTask
такую же обработку:
extension Publisher where Output == [String] {
func toGeographiesDataTask() -> AnyPublisher<URLSession.DataTaskPublisher.Output, Error> {
return self
.map { geographiesRequest(forIds: $0) }
.mapError { $0 as Error }
.flatMap { URLSession.shared.dataTaskPublisher(for: $0).mapError { $0 as Error } }
.eraseToAnyPublisher()
}
}
Вы можете заметить, что toRegionDataTask
и toGeographiesDataTask
теперь почти идентичны. Но я собираюсь оставить это в покое для этого ответа.
Так или иначе, теперь давайте посмотрим на ваш длинный конвейер. Вы получили сообщение об ошибке, потому что ваш toGeographiesDataTask
имел ограничение Failure == Never
, но предшествующий ему оператор map
не имеет Failure
тип Never
. Он имеет тот же тип Failure
, что и восходящий, то есть Error
(из-за оператора decode(type:decoder:)
).
Поскольку я удалил это ограничение из toGeographiesDataTask
, конвейер больше не имеет этой ошибки , Мы можем немного очистить извлечение geoid
:
// Does the abbeviation "subj" really improve the program?
// The subject's Failure type could be anything here.
let subject = PassthroughSubject<MKCoordinateRegion, Error>()
var tickets: [AnyCancellable] = []
subject
.toRegionDataTask()
.map { $0.data }
.decode(type: ApiResponse.self, decoder: JSONDecoder())
.map { $0.body.data(using: .utf8)! }
.decode(type: AmazonResponse.self, decoder: JSONDecoder())
.map { $0.contents }
.map { $0.map { $0.geoid } }
.toGeographiesDataTask()
.map { $0.data }
.decode(type: ApiResponse.self, decoder: JSONDecoder())
.map { $0.body.data(using: .utf8)! }
.decode(type: AmazonResponse.self, decoder: JSONDecoder())
.map { $0.contents }
.sink(
receiveCompletion: { print("completion: \($0)") },
receiveValue: { print("value: \($0)") })
.store(in: &tickets)
let region1 = MKCoordinateRegion()
subject.send(region1)