← Назад к вопросам

Какие знаешь типы Publisher в Combine?

2.7 Senior🔥 191 комментариев
#Многопоточность и асинхронность

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные типы Publisher в Combine

В Combine Publisher — это протокол, который объявляет возможность передавать последовательность значений с течением времени. Он является краеугольным камнем реактивного программирования в iOS/macOS экосистеме. Все Publisher'ы можно классифицировать по их происхождению, поведению и способу создания. Вот основные типы:

1. Встроенные Publisher'ы от Foundation и UIKit/AppKit

Многие классы Apple предоставляют нативные Publisher'ы через расширения, что позволяет легко интегрировать Combine с существующими API.

@Published Property Wrapper

Обертка для свойств класса, которая создает Publisher для изменений значения свойства. Автоматически генерирует Publisher типа Published<T>.Publisher.

class UserProfile {
    @Published var username: String = ""
    @Published var score: Int = 0
}

let profile = UserProfile()
let cancellable = profile.$username
    .sink { newName in
        print("Имя пользователя изменено на: \(newName)")
    }

Timer Publisher

Timer.publish() создает Publisher, который испускает события по таймеру.

let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .common)
let cancellable = timerPublisher
    .autoconnect()
    .sink { date in
        print("Тик: \(date)")
    }

NotificationCenter Publisher

Позволяет подписываться на уведомления как на последовательность событий.

let notificationPublisher = NotificationCenter.default
    .publisher(for: UIApplication.willEnterForegroundNotification)
    .map { _ in "Приложение переходит в foreground" }
    .sink { message in
        print(message)
    }

URLSession Data Task Publisher

Для сетевых запросов. Издает данные или ошибку один раз.

let url = URL(string: "https://api.example.com/data")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
    .map(\.data)
    .decode(type: MyModel.self, decoder: JSONDecoder())

2. Subject'ы – "Мутабельные" Publisher'ы

Subject — это особый тип Publisher, который позволяет "вручную" отправлять значения подписчикам. Они действуют как мост между императивным и реактивным кодом.

PassthroughSubject

Передает значения подписчикам, но не хранит начальное значение. Новые подписчики не получают предыдущих значений.

let subject = PassthroughSubject<String, Never>()
subject.send("Привет") // Отправка значения
let subscription = subject.sink { value in
    print("Получено: \(value)")
}
subject.send("Мир")

CurrentValueSubject

Хранит текущее значение и рассылает его новым подписчикам сразу при подписке. Полезен для представления состояния.

let currentValue = CurrentValueSubject<Int, Never>(0)
print(currentValue.value) // 0
let sub = currentValue.sink { print("Значение: \($0)") } // Сразу печатает: Значение: 0
currentValue.send(1) // Печатает: Значение: 1
print(currentValue.value) // 1

3. Publisher'ы из Swift-последовательностей и коллекций

Любую стандартную коллекцию или последовательность можно легко преобразовать в Publisher.

Just

Издает одно значение и завершается. Полезен для преобразования одиночных значений в реактивный поток.

let justPublisher = Just("Единоразовое значение")
justPublisher.sink(receiveCompletion: { _ in print("Завершено") },
                  receiveValue: { value in print(value) })

Sequence Publisher

Издает элементы последовательности по одному, а затем завершается.

let arrayPublisher = [1, 2, 3, 4, 5].publisher
arrayPublisher
    .filter { $0 % 2 == 0 }
    .sink { evenNumber in
        print("Четное число: \(evenNumber)")
    }

Future

Представляет отложенное вычисление, которое завершится одним значением или ошибкой в будущем. Выполняет свое замыкание только один раз, при первой подписке, и кэширует результат для последующих подписчиков.

func fetchUser() -> Future<User, Error> {
    return Future { promise in
        // Асинхронная операция, например, сетевой запрос
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
            promise(.success(User(name: "Иван")))
        }
    }
}

let futurePublisher = fetchUser()
futurePublisher
    .sink(receiveCompletion: { print($0) },
          receiveValue: { print($0.name) })

Empty и Fail

Специализированные Publisher'ы для конкретных сценариев.

  • Empty: Ничего не издает. Может сразу завершиться успешно или никогда не завершаться (.finished). Используется как заглушка или в цепочках операторов.
  • Fail: Немедленно завершается с указанной ошибкой.
let emptyPublisher = Empty<String, Never>(completeImmediately: true)
let failPublisher = Fail<Data, NetworkError>(error: .timeout)

4. Publisher'ы, созданные операторами Combine

Большинство Publisher'ов в коде создаются не напрямую, а путем преобразования существующих с помощью операторов. Каждый оператор (например, map, filter, flatMap, merge) возвращает новый Publisher.

// Пример цепочки операторов, создающих новые Publisher'ы
let complexPublisher = [1, 2, 3].publisher // Sequence Publisher
    .map { $0 * 10 } // Новый Publisher после трансформации
    .filter { $0 > 15 } // Еще один Publisher после фильтрации
    .flatMap { value in
        // flatMap создает нового Publisher'а для каждого входящего значения
        Just("Число: \(value)")
    }

5. Сторонние и кастомные Publisher'ы

Разработчики могут создавать собственных Publisher, реализуя протокол Publisher вручную (хотя это требуется редко) или используя такие конструкции, как Future, Deferred (который откладывает создание Publisher'а до момента подписки) или AnyPublisher (тип-стиратель).

struct CustomPublisher: Publisher {
    typealias Output = Int
    typealias Failure = Never

    func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
        // Логика отправки значений подписчику
        let subscription = CustomSubscription(subscriber: subscriber)
        subscriber.receive(subscription: subscription)
    }
}

Итог: Экосистема Combine предлагает богатый набор типов Publisher, начиная от простых оберток для свойств и событий системы (@Published, NotificationCenter), через мощные мосты для императивного кода (Subject'ы), до специализированных издателей для значений (Just), асинхронных операций (Future) и ошибок (Fail). Понимание их различий — ключ к эффективному построению реактивных цепочек и архитектур, таких как MVVM или Redux-like стейт-менеджмент.