Result<Int, Error>.Publisher
- это паблишер, генерирует событие либо ошибку всего один раз, а потом замолкает.Just<Int>
- отправляет выходные данные каждому подписчику только один раз, а после завершает работу сfinished
статусом. (Value, Never). Value - если nil, то попадаем в receiveValue. Само value = nil
Fail<Output, Failure>
- не отправляет значения подписчику, а немедленно завершает работу с указанной ошибкой. P.S.: отправляет значение со статусомfinished
если добавитьreplaceError
Optional<Int?>.Publisher
— это паблишер, который генерирует событие всего один раз, а потом замолкает. (Value?, Never). Value? - если nil, то не попадаем в receiveValueEmpty<nil!, Error>
- никогда не отправляет никаких значений и завершает работу немедленно. P.S.: отправляет значение если добавить.replaceEmpty
- Publishers for closure:
Future<Int, Never>
- он начинает выполняться сразу при инициализации, не дожидаясь подписчика. Подписчик может «пропустить» нужное ему событие.Deferred
- что заставить Future вести себя менее эгоистично и дождаться подписчика оборачиваем его в блок Deferred { }.
- Publishers for value:
Subject
— это паблишер, который позволяет отправлять в поток данных события (value
,completion
) извне ручками. Могут поддерживать несколько подписчиков, что означает, что они находятся в отношениях «один ко многим подписчикам»PassthroughSubject
- паблишер, от которого подписчик получит все события, которые сгенерировал паблишер ПОСЛЕ того, как подписчик на него подписался. Этот паблишер не хранит события для подписчиков — «отправил и забыл». Поэтому у него нет первоначального значенияCurrentValueSubject
-@Published
-
- ConnectablePublisher
Autoconnect
-MakeConnectable
-Multicast
-
Open
Result
Result — это паблишер, который генерирует событие либо ошибку. Генерация результата происходит всего 1 раз, после чего паблишер замолкает.
enum ResultError: Error {
case testError
}
let error: ResultError = .testError
let value = 10
var result: Result<Int, Error> = .failure(error)
let resultPublisher: Result<Int, Error>.Publisher = result.publisher
resultPublisher
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("completion status: \(completion)")
case .failure(let error):
print("recieved error: \(error)")
}
}, receiveValue: { value in
print("received value: \(value)")
}
)
Консоль:
recieved error: testError
Если изменим var result: Result<Int, Error> = .success(value)
, то консоль:
received value: 10
completion status: finished
Just
У Just есть несколько особенностей. Во-первых, он всегда генерирует событие (в отличие от опционала). Например, если в качестве output мы поставим опциональный String, а остальной код оставим как есть, то в консоли увидим следующее:
let stringPublisher: Just<String?> = Just(nil)
stringPublisher
.sink(
receiveCompletion: { completion in
print("completion status: \(completion)")
}, receiveValue: { value in
print("recieved value: \(value)")
}
)
Консоль:
recieved value: nil
completion status: finished
Второй особенностью Just является то, что его тип ошибки — Never, то есть он никогда не может завершиться с ошибкой. Даже если мы укажем опциональный тип данных и придет nil, подписчик решит, что это не ошибка и с этими данными можно работать.
Если нам все-таки нужно как-то разграничивать успешный Output и Error, паблишер Just не подойдет. Для этих целей используется паблишер Result.
Fail -
Optional
Его особенностью является то, что если value == nil, то паблишер вообще ничего не пришлет, а в блоке recieveCompletion уведомит о том, что он «всё».
var intValue: Int? = nil
let optionalPublisher: Optional<Int>.Publisher = intValue.publisher
optionalPublisher
.sink(
receiveCompletion: { completion in
print("completion status: \(completion)")
}, receiveValue: { value in
print("received value: \(value)")
}
)
Консоль:
completion status: finished
В то же время, если значение не будет равно nil, то в блоке receiveValue оно уже будет извлечено и нам не надо будет использовать оператор guard или if-let.
var intValue: Int? = 10
Консоль:
received value: 10
completion status: finished
Empty
Помимо Empty()
, вы можете добавить .append(Empty)
к любому издателю, чтобы создать пустой паблишер:
Just(1)
.append(Empty(completeImmediately: false))
.sink(
receiveCompletion: { print("completion: \($0)") },
receiveValue: { print("value: \($0)") })
Output: value: 2
Just(1)
.append(Empty(completeImmediately: true))
.sink(
receiveCompletion: { print("completion: \($0)") },
receiveValue: { print("value: \($0)") })
Output: value: 2 completion: finished
Open
Future
Future<Int, Never>
- в качестве параметра он принимает замыкание, и разработчики могут использовать promise внутри замыкания для отправки результата, выполняется сразу при инициализации, не дожидаясь подписчика. Подписчик может «пропустить» нужное ему событие.
Future
можно использваоть когда мы объединяем некоторые асинхронные задачи в издателе и получаем только один результат. например, получение данных из локальной базы данных или загрузка данных с удаленного сервера.
Future { promise in
AF.request("https://domain/path/to").response { response in
let id model = toModel(response) else {
promise(.success(model))
} else {
promise(.failure(someError))
}
}
}.sink { x in
print(x) /// [model]
}
Open
Subject — это паблишер, который позволяет отправлять в поток данных события извне ручками.
Он выражен протоколом с двумя методами:
.send() — чтобы отправлять в поток конкретные данные.
.send(completion: ) — чтобы уведомить подписчиков о том, что паблишер завершил генерацию данных и больше ничего присылать не будет.
Subject создан для использования в качестве обертки над императивными кусками кода. В отличие от Future, им удобно оборачивать свойства, а не методы с @escaping closure. Еще одно отличие от Future заключается в том, что Subject не является one-shot и мы можем отправлять в Data-stream сколько угодно событий.
У этого паблишера есть 2 имплементации: PassthroughSubject
и CurrentValueSubject
.
Кстати, в случае с обоими паблишерами Subject, если мы попробуем отправить после комплишена еще какое-нибудь значение, то ничего не произойдет (ни крашей, ни ошибок компилятора), а подписчики просто его не получат.
subject.send(4)
subject.send(completion: .finished)
subject.send(10)
PassthroughSubject
PassthroughSubject — паблишер, от которого подписчик получит все события, которые сгенерировал паблишер ПОСЛЕ того, как подписчик на него подписался. Этот паблишер не хранит события для подписчиков — «отправил и забыл». Поэтому у него нет первоначального значения:
let passthru = PassthroughSubject<Int, Never>()
passthru.send(2)
passthru.send(3)
let c1 = passthru.sink { print($0) }
let c2 = passthru.sink { print($0) }
let c3 = passthru.sink { print($0) }
passthru.send(4)
passthru.send(completion: .finished)
Вывод:
4 4 4
На примере выше мы инициализировали паблишер, который генерит объекты типа Int и не может зафейлиться.
Отправили с помощью метода .send объекты (2,3), после чего подписались, отправили еще один объект (4) и в конце отправили подписчикам событие того, что паблишер завершил генерацию событий. Но в консоль вывелось только 2 последних события: объект типа Int и сообщение finished.
CurrentValueSubject
CurrentValueSubject — почти тоже самое, что и PassthroughSubject, но с рядом отличий:
-
При инициализации имеет начальное значение и обновляет его при вызове методов отправки. Это гарантирует, что подписчикам будет доставлено последнее значение сразу после оформления подписки.
-
У паблишера есть свойство value, которое хранит в себе последнее актуальное значение. Мы можем к нему обратиться через точечный синтаксис.
-
Подписчик не только получает от паблишера все новые значения ПОСЛЕ подписки, но и то значение, которое в себе хранит паблишер ДО того, как на него подписался подписчик (это может быть первоначальное значение, указанное при инициализации паблишера, либо последнее сгенерированное событие, если паблишер успел что-то отправить в поток до того, как на него кто-то подписался.
-
У него есть буфер для оптимизации — если паблишер часто генерит одни и те же значения.
let currentVal = CurrentValueSubject<Int, Never>(1) // затерлось .send(2)
currentVal.send(2) // затерлось .send(3)
currentVal.send(3)
let c1 = currentVal.sink { print($0) }
let c2 = currentVal.sink { print($0) }
let c3 = currentVal.sink { print($0) }
currentVal.send(4)
Вывод:
3 3 3 4 4 4
Open
Autoconnect
Издатель AutoConnect
используется для автоматического «открытия клапана» при вызове метода подписки (sink
).
let timerPublisher = Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
let cancellable = timerPublisher.sink { value in
print(value)
}
Звучит бесполезно, но это полезно, когда нам нужно отменить подключаемую функцию, если ее восходящим потоком является ConnectablePublisher:
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
cancell.cancel()
}
Избегайте автоматического подключения, если порядок подписки имеет решающее значение, избегайте использования автоматического подключения, поскольку это может привести к недетерминированному поведению. Вместо этого вручную подключите издателя с помощью метода Connect().
MakeConnectable
Напротив, предположим, что у вас есть приложение, которое отображает актуальную информацию о погоде. ConnectablePublisher предоставляет информацию о погоде. Но предположим, что перед отображением информации о погоде происходит некоторый сложный рендеринг или анимация пользовательского интерфейса. В этом случае вы можете вызвать метод connect()
после завершения обновлений пользовательского интерфейса, чтобы пользователю была предоставлена самая последняя информация. `AutoConnect здесь не подойдет.
let passthru = PassthroughSubject<Int, Never>()
let connectable = passthru.makeConnectable()
let cancellable1 = connectable.sink { x in
print(x) /// [3,4]
}
passthru.send(1)
passthru.send(2)
let cancellable2 = connectable.connect()
passthru.send(3)
passthru.send(4)
Когда вызывается метод connect
, мы обновляем флаг connected до true и вызываем закрытие, чтобы установить отношения подписки, которые "откроют клапан". Возвращаемая cancellable используется, чтобы решить, когда уничтожить (cancel
) отношения, которые "закроют клапан".ё
Multicast
Multicast полезен, когда мы хотим преобразовать обычного издателя в subject
, который может отправлять событие нескольким подписчикам одновременно.
let multi = [ 1 , 2 , 3 , 4 ].publisher.multicast(subject: PassthroughSubject ())
let c1 = multi.sink { x in
print ( "sink1 \(x) " ) /// [1,2,3,4]
}
let c2 = multi.sink { x in
print ( "sink2 \(x) " ) /// [1,2,3,4]
}
let c3 = multi.sink { x в
печати ( "sink3 \(x) " ) /// [1,2,3,4]
}
let cancellable = multi.connect()
Open
Чаще всего @Published используется для изменения свойств ObservableObject, потому что @Published создаст внутри себя CurrentValueSubject, который будет отправлять события каждый раз, когда это свойство изменяется.
ObservableObject использует в связке с @Published для агрегирования всех событий, сгенерированных @Published, что означает, что мы можем подписаться на издателя ObservableObject вместо подписки на каждое из его @Published.
Издатели (SwiftUI предлагает StateObject, ObservedObject и EnvironmentObject, все из которых подписываются на агрегированного издателя ObservableObject и автоматически обновляют пользовательский интерфейс).
Имплементация @Published:
@propertyWrapper
struct Published<T> {
let currentValue: CurrentValueSubject<T, Never>
init(wrappedValue: T) {
currentValue = CurrentValueSubject(wrappedValue)
}
var wrappedValue: T {
get { currentValue.value }
set { currentValue.send(newValue) }
}
var projectedValue: CurrentValueSubject<T, Never> { currentValue }
}
4.1.4.2 Publisher Theme | Back To iOSWiki Contents | 4.1.4.4 Operators Theme