Combine Reactive Programming – AdventureTube iOS

Reactive Data Flow with Apple’s Combine Framework

Combine Overview

Combine is Apple’s framework for processing values over time, enabling reactive programming in Swift.

Why Combine in AdventureTube?

Reactive UI – Automatic view updates via @Published
Async Operations – Handle network requests reactively
Core Data Integration – Cross-context observation
Composable – Chain and transform data streams
Type-Safe – Compile-time error checking
Memory Safe – Automatic subscription cleanup

Core Concepts

Publisher

Definition: Emits values over time

// Examples of Publishers in AdventureTube
@Published var stories: [StoryEntity] = []  // Published<[StoryEntity]>
URLSession.shared.dataTaskPublisher(for: url)  // DataTaskPublisher
CoreDataFetchResultsPublisher  // Custom publisher

Subscriber

Definition: Receives values from a publisher

// Sink is the most common subscriber
publisher.sink(
    receiveCompletion: { completion in
        // Handle completion or error
    },
    receiveValue: { value in
        // Handle received value
    }
)

AnyCancellable

Definition: Token that cancels subscription when deallocated

private var cancellables = Set()

publisher.sink { ... }
    .store(in: &cancellables)  // Stores subscription

Publishers in AdventureTube

1. @Published (Most Common)

Location: ViewModels
Purpose: Auto-notify observers when property changes

class MyStoryListViewVM: ObservableObject {
    @Published var youtubeContentItems: [YoutubeContentItem] = []
    @Published var isLoading = false
    @Published var errorMessage: String?
}

2. URLSession.DataTaskPublisher

Location: API Services
Purpose: Network requests

3. PassthroughSubject

Location: Custom publishers, event streams
Purpose: Manually send values

private let playListIdPublisher = PassthroughSubject()

// Send value
playListIdPublisher.send("UUMg4QJXtDH-VeoJvlEpfEYg")

// Send completion
playListIdPublisher.send(completion: .finished)

Common Patterns

Pattern 1: Chaining Network Requests

func fetchChannelThenVideos() {
    fetchChannelInfo()
        .flatMap { channelInfo in
            return self.fetchVideos(playlistId: channelInfo.uploadsPlaylistId)
        }
        .sink(
            receiveCompletion: { completion in
                // Handle final completion
            },
            receiveValue: { videos in
                // Process videos
            }
        )
        .store(in: &cancellables)
}

Pattern 2: Debouncing Search Input

class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [Result] = []

    private var cancellables = Set()

    init() {
        $searchText
            .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }
}

Memory Management

AnyCancellable Storage

// BAD - Cancels immediately
publisher.sink { value in
    print(value)
}

// GOOD - Stored, cancels when viewModel deallocates
publisher.sink { value in
    print(value)
}
.store(in: &cancellables)

Weak Self Pattern

// GOOD - No retain cycle
publisher.sink { [weak self] value in
    self?.processValue(value)
}
.store(in: &cancellables)

Operator Cheat Sheet

Operator Purpose Example
map Transform values .map { $0.uppercased() }
filter Filter values .filter { $0.count > 5 }
compactMap Map + remove nils .compactMap { Int($0) }
flatMap Chain publishers .flatMap { fetchUser($0) }
debounce Delay emissions .debounce(for: .seconds(1), ...)
retry Retry on failure .retry(3)
catch Handle errors .catch { Just([]) }

Best Practices

✅ Do

  1. Store cancellables: .store(in: &cancellables)
  2. Use [weak self]: Prevent retain cycles
  3. Handle both success and failure
  4. Use appropriate schedulers: .receive(on: DispatchQueue.main) for UI
  5. Type erase when needed: .eraseToAnyPublisher()

❌ Don’t

  1. Don’t forget to store subscriptions
  2. Don’t create retain cycles
  3. Don’t perform UI updates on background threads
  4. Don’t ignore errors
  5. Don’t over-complicate with operators
]]>

Leave a Comment

Your email address will not be published. Required fields are marked *