## Convenience Publishers

1. `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, то не попадаем в receiveValue
    + `Empty<nil!, Error>` - никогда не отправляет никаких значений и завершает работу немедленно. P.S.: отправляет значение если добавить `.replaceEmpty`
    ---
2. Publishers for closure:
    + `Future<Int, Never>` - он начинает выполняться сразу при инициализации, не дожидаясь подписчика. Подписчик может «пропустить» нужное ему событие.
    + `Deferred` - что заставить Future вести себя менее эгоистично и дождаться подписчика оборачиваем его в блок Deferred { }.
3. Publishers for value: `Subject` — это паблишер, который позволяет отправлять в поток данных события (`value`, `completion`) извне ручками. Могут поддерживать несколько подписчиков, что означает, что они находятся в отношениях «один ко многим подписчикам»
    + `PassthroughSubject` - паблишер, от которого подписчик получит все события, которые сгенерировал паблишер ПОСЛЕ того, как подписчик на него подписался. Этот паблишер не хранит события для подписчиков — «отправил и забыл». Поэтому у него нет первоначального значения
    + `CurrentValueSubject` - 
    + `@Published` - 
4. ConnectablePublisher
    + `Autoconnect` - 
    + `MakeConnectable` - 
    + `Multicast` - 
    
### 1. Combine Result Publishers

<details><summary>Open</summary>
<p>

<details><summary>Result</summary>
<p>

Result — это паблишер, который генерирует событие либо ошибку. Генерация результата происходит всего 1 раз, после чего паблишер замолкает.

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

Консоль:

```swift
recieved error: testError
```

Если изменим `var result: Result<Int, Error> = .success(value)`, то консоль:
```swift
received value: 10 
completion status: finished
```

</p>
</details>

---

<details><summary>Just</summary>
<p>

У Just есть несколько особенностей. Во-первых, он всегда генерирует событие (в отличие от опционала). Например, если в качестве output мы поставим опциональный String, а остальной код оставим как есть, то в консоли увидим следующее:

```swift
let stringPublisher: Just<String?> = Just(nil)

stringPublisher
    .sink(
        receiveCompletion: { completion in
            print("completion status: \(completion)")
        }, receiveValue: { value in
            print("recieved value: \(value)")
        }
    )
```

Консоль:

```swift
recieved value: nil
completion status: finished
```

Второй особенностью Just является то, что его тип ошибки — Never, то есть он никогда не может завершиться с ошибкой. Даже если мы укажем опциональный тип данных и придет nil, подписчик решит, что это не ошибка и с этими данными можно работать.

Если нам все-таки нужно как-то разграничивать успешный Output и Error, паблишер Just не подойдет. Для этих целей используется паблишер Result.

</p>
</details>

---

<details><summary>Fail -</summary>
<p>

</p>
</details>

---

<details><summary>Optional</summary>
<p>

Его особенностью является то, что если value == nil, то паблишер вообще ничего не пришлет, а в блоке recieveCompletion уведомит о том, что он «всё».

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

Консоль:
```swift
completion status: finished
```

В то же время, если значение не будет равно nil, то в блоке receiveValue оно уже будет извлечено и нам не надо будет использовать оператор guard или if-let.

```swift
var intValue: Int? = 10
```

Консоль:
```swift
received value: 10
completion status: finished
```

</p>
</details>

---

<details><summary>Empty</summary>
<p>

Помимо `Empty()`, вы можете добавить `.append(Empty)` к любому издателю, чтобы создать пустой паблишер:

```swift
Just(1)
    .append(Empty(completeImmediately: false))
    .sink(
        receiveCompletion: { print("completion: \($0)") },
        receiveValue: { print("value: \($0)") })
```

Output: `value: 2`

```swift
Just(1)
    .append(Empty(completeImmediately: true))
    .sink(
        receiveCompletion: { print("completion: \($0)") },
        receiveValue: { print("value: \($0)") })
```

Output: `value: 2 completion: finished`

</p>
</details>

---

</p>
</details>

### 2. Publishers for closure: Future, Deferred

<details><summary>Open</summary>
<p>

---

<details><summary>Future</summary>
<p>

`Future<Int, Never>` - в качестве параметра он принимает замыкание, и разработчики могут использовать promise внутри замыкания для отправки результата, выполняется сразу при инициализации, не дожидаясь подписчика. Подписчик может «пропустить» нужное ему событие.

`Future` можно использваоть когда мы объединяем некоторые асинхронные задачи в издателе и получаем только один результат. например, получение данных из локальной базы данных или загрузка данных с удаленного сервера.

```swift
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]
}
```

</p>
</details>

---

<details><summary>Deferred-</summary>
<p>

1. [Использование промисов и фьючерсов в сочетании](https://www.donnywals.com/using-promises-and-futures-in-combine/)

</p>
</details>

---

</p>
</details>


### 3. Publishers for value: __Subject__

<details><summary>Open</summary>
<p>

**Subject** — это паблишер, который позволяет отправлять в поток данных события извне ручками.

Он выражен протоколом с двумя методами:

**.send()** —  чтобы отправлять в поток конкретные данные.

**.send(completion: )** — чтобы уведомить подписчиков о том, что паблишер завершил генерацию данных и больше ничего присылать не будет.

**Subject** создан для использования в качестве обертки над императивными кусками кода. В отличие от Future, им удобно оборачивать свойства, а не методы с @escaping closure. Еще одно отличие от Future заключается в том, что Subject не является one-shot и мы можем отправлять в Data-stream сколько угодно событий.

У этого паблишера есть 2 имплементации: `PassthroughSubject` и `CurrentValueSubject`.

Кстати, в случае с обоими паблишерами Subject, если мы попробуем отправить после комплишена еще какое-нибудь значение, то ничего не произойдет (ни крашей, ни ошибок компилятора), а подписчики просто его не получат.

```swift
subject.send(4)
subject.send(completion: .finished)
subject.send(10)
```

<details><summary>PassthroughSubject</summary>
<p>

PassthroughSubject — паблишер, от которого подписчик получит все события, которые сгенерировал паблишер ПОСЛЕ того, как подписчик на него подписался. Этот паблишер не хранит события для подписчиков — «отправил и забыл». Поэтому у него нет первоначального значения:

```swift
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)
```

Вывод:

```swift
4 4 4
```

На примере выше мы инициализировали паблишер, который генерит объекты типа Int и не может зафейлиться.

Отправили с помощью метода .send объекты (2,3), после чего подписались, отправили еще один объект (4) и в конце отправили подписчикам событие того, что паблишер завершил генерацию событий. Но в консоль вывелось только 2 последних события: объект типа Int и сообщение finished.

</p>
</details>

---

<details><summary>CurrentValueSubject</summary>
<p>

CurrentValueSubject — почти тоже самое, что и PassthroughSubject, но с рядом отличий:

1. При инициализации имеет начальное значение и обновляет его при вызове методов отправки. Это гарантирует, что подписчикам будет доставлено последнее значение сразу после оформления подписки.

2. У паблишера есть свойство value, которое хранит в себе последнее актуальное значение. Мы можем к нему обратиться через точечный синтаксис.

3. Подписчик не только получает от паблишера все новые значения ПОСЛЕ подписки, но и то значение, которое в себе хранит паблишер ДО того, как на него подписался подписчик (это может быть первоначальное значение, указанное при инициализации паблишера, либо последнее сгенерированное событие, если паблишер успел что-то отправить в поток до того, как на него кто-то подписался.

4. У него есть буфер для оптимизации — если паблишер часто генерит одни и те же значения.


```swift
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)
```

Вывод:

```swift
3 3 3 4 4 4
```

</p>
</details>

---

</p>
</details>

### 4. ConnectablePublisher

<details><summary>Open</summary>
<p>

<details><summary>Autoconnect</summary>
<p>

Издатель `AutoConnect` используется для автоматического «открытия клапана» при вызове метода подписки (`sink`).

```swift
let timerPublisher = Timer.publish(every: 1, on: .main, in: .common)
    .autoconnect()

let cancellable = timerPublisher.sink { value in
    print(value)
}
```

Звучит бесполезно, но это полезно, когда нам нужно отменить подключаемую функцию, если ее восходящим потоком является ConnectablePublisher:

```swift
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
    cancell.cancel()
}
```

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



</p>
</details>

---

<details><summary>MakeConnectable</summary>
<p>

Напротив, предположим, что у вас есть приложение, которое отображает актуальную информацию о погоде. ConnectablePublisher предоставляет информацию о погоде. Но предположим, что перед отображением информации о погоде происходит некоторый сложный рендеринг или анимация пользовательского интерфейса. В этом случае вы можете вызвать метод `connect()` после завершения обновлений пользовательского интерфейса, чтобы пользователю была предоставлена самая последняя информация. `AutoConnect здесь не подойдет.

```swift
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`) отношения, которые "закроют клапан".ё

</p>
</details>

---

<details><summary>Multicast</summary>
<p>

Multicast  полезен, когда мы хотим преобразовать обычного издателя в `subject`, который может отправлять событие нескольким подписчикам одновременно.

```swift
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()
```

</p>
</details>

</p>
</details>


#### @Published

<details><summary>Open</summary>
<p>

Чаще всего @Published используется для изменения свойств ObservableObject, потому что @Published создаст внутри себя CurrentValueSubject, который будет отправлять события каждый раз, когда это свойство изменяется.

ObservableObject использует в связке с @Published для агрегирования всех событий, сгенерированных @Published, что означает, что мы можем подписаться на издателя ObservableObject вместо подписки на каждое из его @Published. 

Издатели (SwiftUI предлагает StateObject, ObservedObject и EnvironmentObject, все из которых подписываются на агрегированного издателя ObservableObject и автоматически обновляют пользовательский интерфейс).

Имплементация @Published:

```swift
@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 }
}
```

</p>
</details>

---

[4.1.4.2 Publisher Theme](./4.1.4.2%20Publishers.md) | [Back To iTWiki Contents](https://github.com/eldaroid/iTWiki) | [4.1.4.4 Operators Theme](./4.1.4.4%20Operators.md)
