Combine-常见使用场景

26 阅读2分钟

介绍

在现代 iOS 开发中,响应式编程日益重要。Apple 推出的 Combine 框架为开发者提供了强大的声明式 API,用于处理异步事件流。本文将结合常见场景,逐一展示 Combine 的实际用法,包括网络请求、输入控制、定时器、通知监听、异步任务处理以及视图控制器之间的逆向传值。

网络请求

通过 Combine 可以优雅地封装网络请求流程。

let url = URL(string: "https://api.example.com/data")!
// Publisher
let publisher = URLSession.shared.dataTaskPublisher(for: url)
    .map { $0.data }
    .decode(type: Response.self, decoder: JSONDecoder())
    .eraseToAnyPublisher()
// 订阅
let cancellable = publisher
    .sink(receiveCompletion: { completion in
        switch completion {
        case .failure(let error):
            print("Error: \(error)")
        case .finished:
            break
        }
    }, receiveValue: { response in
        print("Response: \(response)")
    })

控制输入

结合 @Published 和 debounce,可以高效地处理用户输入,避免频繁触发操作。

class ViewController: UIViewController {
    @Published var text = ""
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        $text
            .debounce(for: 0.5, scheduler: DispatchQueue.main)
            .sink { [weak self] value in
                guard let self = self else { return }
                self.processInput(value)
            }
            .store(in: &cancellables)
    }

    func processInput(_ input: String) {
        // 处理输入内容
        print("Input: \(input)")
    }
}

定时器

使用 Combine 的 Timer.publish 可以轻松创建定时器。

private var subscription: AnyCancellable?

subscription = Timer
            .publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .scan(0) { count, _ in // 累加,count为闭包最后一次返回的值
                count + 1
            }
            .sink(receiveCompletion: { _ in
                print("finish")
            }, receiveValue: { [weak self] count in // 操作UI时,[weak self]不可少
                guard let self = self else { return }
                self.countLbl.text = count.format
            })

通知

借助 NotificationCenter 和 Combine,可以优雅地响应通知事件。

private var subscriptions = Set<AnyCancellable>()

NotificationCenter
            .default
            .publisher(for: UITextField.textDidChangeNotification, object: inputTxtField) // 监听输入
            .compactMap { ($0.object as? UITextField)?.text } // 此时的publisher是通知
            .map { "The user entered: \($0)" }
            .assign(to: \.text, on: textLbl)
            .store(in: &subscriptions)

异步

通过 Future 可以将传统的回调封装为 Combine 的 Publisher。

// Publisher
func authorize() -> AnyPublisher<Bool, Error> {
        // 延期Publisher等待订阅
        Deferred {
            // 任何异步操作都可以包进Future
            Future { promise in
                UNUserNotificationCenter
                    .current()
                    .requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
                        // Promise处理
                        if let error = error {
                            // AnyPublisher的第2个参数
                            promise(.failure(error))
                        } else {
                            // AnyPublisher的第1个参数
                            promise(.success(granted))
                        }
                    }
            }
        }
        .eraseToAnyPublisher()
    }
    
private var subscriptions = Set<AnyCancellable>()
// 订阅
authorize() // AnyPublisher
            .replaceError(with: false) // 异常处理
            .receive(on: DispatchQueue.main)
            .sink { [weak self] val in
                guard let self = self else { return }
                self.permissionLbl.text = "Status: \(val ? "Granted" : "Denied")"
            }
            .store(in: &subscriptions)

UIViewController逆向传值

当需要从下一个页面传值回当前页面时,Combine 提供了比 delegate 更优雅的方式。

// 接收值的UIViewController
let nextViewController = NextViewController()
let publisher = PassthroughSubject<String, Never>()
nextViewController.publisher = publisher

subscription = publisher.sink { [weak self] info in
    guard let self = self else { return }
    // 处理info
}

present(nextViewController, animated: true)



// 传递值的UIViewController
var publisher: PassthroughSubject<String, Never>?

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    publisher?.send("传值")
    dismiss(animated: true) {
        self.publisher?.send(completion: .finished)
    }
}