SwiftUI 与 Combine 框架紧密集成,提供了响应式的数据流管理方案。Combine 通过发布者和订阅者模式处理异步事件和数据流。

重点:

  • Combine 框架基础概念
  • Publisher 和 Subscriber 的使用
  • 常用操作符
  • 错误处理
  • 实际应用场景

Combine 基础 Link to heading

  1. Publisher 和 Subscriber:
// 定义发布者
let publisher = [1, 2, 3, 4, 5].publisher

// 基本订阅
publisher
    .sink { completion in
        print("完成: \(completion)")
    } receiveValue: { value in
        print("收到值: \(value)")
    }
  1. Subject 的使用:
class DataManager {
    let valuePublisher = CurrentValueSubject<Int, Never>(0)
    let eventPublisher = PassthroughSubject<String, Never>()
    
    func updateValue() {
        valuePublisher.send(valuePublisher.value + 1)
    }
    
    func triggerEvent() {
        eventPublisher.send("事件触发")
    }
}

数据转换 Link to heading

  1. 基础操作符:
let numbers = [1, 2, 3, 4, 5].publisher

numbers
    .map { $0 * 2 }         // 转换值
    .filter { $0 > 5 }      // 过滤
    .collect()              // 收集到数组
    .sink { values in
        print("结果: \(values)")
    }
  1. 组合操作符:
let publisher1 = [1, 2, 3].publisher
let publisher2 = [4, 5, 6].publisher

Publishers.CombineLatest(publisher1, publisher2)
    .map { value1, value2 in
        return value1 + value2
    }
    .sink { sum in
        print("和: \(sum)")
    }

异步操作处理 Link to heading

  1. 网络请求:
struct NetworkManager {
    func fetchData() -> AnyPublisher<Data, Error> {
        guard let url = URL(string: "https://api.example.com/data") else {
            return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
        }
        
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }
}

class ViewModel: ObservableObject {
    @Published var data: Data?
    private var cancellables = Set<AnyCancellable>()
    
    func loadData() {
        NetworkManager().fetchData()
            .receive(on: DispatchQueue.main)
            .sink { completion in
                switch completion {
                case .finished:
                    print("加载完成")
                case .failure(let error):
                    print("错误: \(error)")
                }
            } receiveValue: { [weak self] data in
                self?.data = data
            }
            .store(in: &cancellables)
    }
}
  1. 定时器:
class TimerViewModel: ObservableObject {
    @Published var count = 0
    private var cancellables = Set<AnyCancellable>()
    
    func startTimer() {
        Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                self?.count += 1
            }
            .store(in: &cancellables)
    }
}

错误处理 Link to heading

  1. 错误恢复:
struct DataService {
    enum DataError: Error {
        case fetchFailed
        case invalidData
    }
    
    func fetchData() -> AnyPublisher<String, DataError> {
        Fail(error: DataError.fetchFailed)
            .delay(for: 1, scheduler: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

// 使用错误恢复
DataService().fetchData()
    .catch { error -> Just<String> in
        // 发生错误时返回默认值
        return Just("默认值")
    }
    .sink { value in
        print("结果: \(value)")
    }
  1. 重试机制:
struct RetryableDataService {
    func fetchWithRetry() -> AnyPublisher<Data, Error> {
        URLSession.shared.dataTaskPublisher(for: URL(string: "https://api.example.com")!)
            .retry(3)  // 最多重试3次
            .map(\.data)
            .mapError { $0 as Error }
            .eraseToAnyPublisher()
    }
}

实际应用 Link to heading

  1. 表单验证:
class FormViewModel: ObservableObject {
    @Published var username = ""
    @Published var password = ""
    @Published var isValid = false
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        Publishers.CombineLatest($username, $password)
            .map { username, password in
                return username.count >= 4 && password.count >= 6
            }
            .assign(to: \.isValid, on: self)
            .store(in: &cancellables)
    }
}
  1. 搜索功能:
class SearchViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [String] = []
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .filter { !$0.isEmpty }
            .sink { [weak self] text in
                self?.performSearch(text)
            }
            .store(in: &cancellables)
    }
    
    private func performSearch(_ query: String) {
        // 执行搜索操作
    }
}

性能考虑 Link to heading

  1. 内存管理:
class ResourceManager {
    private var cancellables = Set<AnyCancellable>()
    
    func subscribe() {
        // 使用 weak self 避免循环引用
        somePublisher
            .sink { [weak self] value in
                self?.process(value)
            }
            .store(in: &cancellables)
    }
    
    deinit {
        // cancellables 会自动取消所有订阅
        print("资源释放")
    }
}
  1. 订阅限制:
class DataStreamManager {
    private var cancellables = Set<AnyCancellable>()
    
    func startStream() {
        somePublisher
            .buffer(size: 10, prefetch: .byRequest, whenFull: .dropOldest)
            .sink { value in
                // 处理数据
            }
            .store(in: &cancellables)
    }
}

Combine 框架为 SwiftUI 提供了强大的数据流管理能力。通过发布者和订阅者模式,可以优雅地处理异步操作和状态管理。在实际开发中,合理使用 Combine 的各种操作符可以简化代码逻辑,提高应用的响应性和可维护性。