Combine-常見使用場景

介紹

在現代 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)
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。