Почему Combine получает ошибки проглатывания оператора (on :)?

Следующий конвейер:

enum MyError: Error {
  case oops
}
let cancel = Fail<Int, Error>(error: MyError.oops)
  .print("1>")
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

Выходы:

1>: receive subscription: (Empty)
2>: receive subscription: (Print)
2>: request unlimited
1>: request unlimited
1>: receive error: (oops)
2>: receive error: (oops)
status> failure(__lldb_expr_126.MyError.oops)

Эта проблема

Однако, если я вставлю оператор receive(on:) в предыдущий конвейер:

enum MyError: Error {
  case oops
}
let cancel = Fail<Int, Error>(error: MyError.oops)
  .print("1>")
  .receive(on: RunLoop.main)
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

вывод:

1>: receive subscription: (Empty)
1>: receive error: (oops)

Оператор receive, кажется, закорачивает конвейер. Я не видел, чтобы это происходило с другими издателями, только когда я использую издателя Fail или PassthroughSubject.

Это ожидаемое поведение? Если да, то в чем причина?


Обходной путь

Вот пример создания неудачного издателя, который работает с receive(on:) издателем:

struct FooModel: Codable {
  let title: String
}

func failPublisher() -> AnyPublisher<FooModel, Error> {
  return Just(Data(base64Encoded: "")!)
    .decode(type: FooModel.self, decoder: JSONDecoder())
    .eraseToAnyPublisher()
}

let cancel = failPublisher()
  .print("1>")
  .receive(on: RunLoop.main)
  .print("2>")
  .sink(receiveCompletion: { status in
    print("status>", status)
  }) { value in
    print("value>", value)
}

person Gil Birman    schedule 20.11.2019    source источник


Ответы (1)


Возможно, вы столкнулись с той же проблемой, которая описана в этот пост. Очевидно, receive(on:) будет отправлять все сообщения асинхронно через указанный планировщик, включая сообщения подписки. Итак, что происходит, ошибка отправляется до того, как событие подписки может быть отправлено асинхронно, и поэтому к издателю receive не привязан подписчик, когда приходит следующее событие.

Однако похоже, что они меняют это с бета-версия 1 для разработчиков iOS 13.3:

Начиная с бета-версии 1 для разработчиков iOS 13.3 (и связанных выпусков для других платформ), мы изменили поведение приема (on :) плюс других операторов планировщика, чтобы они синхронно отправляли свою подписку вниз по течению. Раньше они выполняли "асинхронную" синхронизацию с предоставленным планировщиком.

person jjoelson    schedule 21.11.2019
comment
Ага! Таким образом, обходной путь предположительно работает потому, что .decode(type:decoder:) отправляет сообщение асинхронно. - person Gil Birman; 22.11.2019
comment
хм, может быть. Я не знаю, в документации для decode ничего не говорится об асинхронной работе. tbh Я не понимаю, почему ваш обходной путь работает - person jjoelson; 22.11.2019