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 *

Scroll to Top