Swift Combine 通过包装基于 delegate 的 API 创建重复发布者 从入门到精通十八

本文详细介绍了SwiftCombine从基础到进阶的内容,包括发布者、订阅者、操作符、生命周期管理,以及如何包装CoreLocationAPI,使用Future处理异步请求,处理网络受限和用户输入等。通过实例展示了如何在UIKit中集成和使用SwiftCombine进行开发。
摘要由CSDN通过智能技术生成

Combine 系列

  1. Swift Combine 从入门到精通一
  2. Swift Combine 发布者订阅者操作者 从入门到精通二
  3. Swift Combine 管道 从入门到精通三
  4. Swift Combine 发布者publisher的生命周期 从入门到精通四
  5. Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
  6. Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
  7. Swift 使用 Combine 进行开发 从入门到精通七
  8. Swift 使用 Combine 管道和线程进行开发 从入门到精通八
  9. Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
  10. Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
  11. Swift Combine 用 Future 来封装异步请求 从入门到精通十一
  12. Swift Combine 有序的异步操作 从入门到精通十二
  13. Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
  14. Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
  15. Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
  16. Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
  17. Swift Combine 合并多个管道以更新 UI 元素 从入门到精通十七
    在这里插入图片描述

1. 通过包装基于 delegate 的 API 创建重复发布者

目的: 将 Apple delegate API 之一包装为 Combine 管道来提供值。

Future 发布者非常适合包装现有代码以发出单个请求,但它不适用于产生冗长或可能无限量输出的发布者。

Apple 的 Cocoa API 倾向于使用对象/代理模式,你可以选择接收任意数量的不同回调(通常包含数据)。 其中一个例子是在 CoreLocation 库中,提供了许多不同的数据源。

如果你想在管道中使用此类 API 之一提供的数据,你可以将对象包装起来,并使用 passthroughSubject 来暴露发布者。 下面的示例代码显示了一个包装 CoreLocation 中 CLManager 的对象并通过 UIKit 的 ViewController 消费其数据的示例。
在这里插入图片描述

UIKit-Combine/LocationHeadingProxy.swift

import Foundation
import Combine
import CoreLocation

final class LocationHeadingProxy: NSObject, CLLocationManagerDelegate {

    let mgr: CLLocationManager  // 1
    private let headingPublisher: PassthroughSubject<CLHeading, Error>  // 2
    var publisher: AnyPublisher<CLHeading, Error>  // 3

    override init() {
        mgr = CLLocationManager()
        headingPublisher = PassthroughSubject<CLHeading, Error>()
        publisher = headingPublisher.eraseToAnyPublisher()

        super.init()
        mgr.delegate = self  // 4
    }

    func enable() {
        mgr.startUpdatingHeading()  // 5
    }

    func disable() {
        mgr.stopUpdatingHeading()
    }
    // MARK - delegate methods

    /*
     *  locationManager:didUpdateHeading:
     *
     *  Discussion:
     *    Invoked when a new heading is available.
     */
    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        headingPublisher.send(newHeading)  // 6
    }

    /*
     *  locationManager:didFailWithError:
     *  Discussion:
     *    Invoked when an error has occurred. Error types are defined in "CLError.h".
     */
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        headingPublisher.send(completion: Subscribers.Completion.failure(error))  // 7
    }
}
  1. CLLocationManager 作为 CoreLocation 的一部分,是被包装的核心。 因为要使用该框架,它有其他方法需要被调用,因此我将它暴露为一个 public 的只读属性。 这对于先请求用户许可然后使用位置 API 很有用,框架将该位置 API 暴露为一个在 CLLocationManager 上的方法。
  2. 使用一个具有我们要发布的数据类型的 private 的 PassthroughSubject 实例,来提供我们的类内部访问以转发数据。
  3. 一个 public 的属性 publisher 将来自上面的 subject 的发布者暴露给外部以供订阅。
  4. 其核心是将该类指定为 CLLocationManager 实例的代理,在该实例初始化的尾端进行设置。
  5. CoreLocation API 不会立即开始发送信息。 有些方法需要调用才能启动(并停止)数据流,这些方法被包装并暴露在此 LocationHeadingProxy 对象上。 大多数发布者都设置为订阅并根据订阅驱动消费,因此这有点不符合发布者如何开始生成数据的规范。
  6. 在定义代理和激活 CLLocationManager 后,数据将通过在 CLLocationManagerDelegate 上定义的回调提供。 我们为这个包装的对象实现了我们想要的回调,并在其中使用 passthroughSubject.send() 将信息转发给任何现有的订阅者。
  7. 虽然没有严格要求,但代理提供了 Error 上报回调,因此我们也将其包括在示例中通过 passthroughSubject 转发。

2. UIKit-Combine/HeadingViewController.swift

import UIKit
import Combine
import CoreLocation

class HeadingViewController: UIViewController {

    var headingSubscriber: AnyCancellable?

    let coreLocationProxy = LocationHeadingProxy()
    var headingBackgroundQueue: DispatchQueue = DispatchQueue(label: "headingBackgroundQueue")

    // MARK - lifecycle methods

    @IBOutlet weak var permissionButton: UIButton!
    @IBOutlet weak var activateTrackingSwitch: UISwitch!
    @IBOutlet weak var headingLabel: UILabel!
    @IBOutlet weak var locationPermissionLabel: UILabel!

    @IBAction func requestPermission(_ sender: UIButton) {
        print("requesting corelocation permission")
        let _ = Future<Int, Never> { promise in  // 1
            self.coreLocationProxy.mgr.requestWhenInUseAuthorization()
            return promise(.success(1))
        }
        .delay(for: 2.0, scheduler: headingBackgroundQueue) // 2
        .receive(on: RunLoop.main)
        .sink { _ in
            print("updating corelocation permission label")
            self.updatePermissionStatus()  // 3
        }
    }

    @IBAction func trackingToggled(_ sender: UISwitch) {
        switch sender.isOn {
        case true:
            self.coreLocationProxy.enable()  // 4
            print("Enabling heading tracking")
        case false:
            self.coreLocationProxy.disable()
            print("Disabling heading tracking")
        }
    }

    func updatePermissionStatus() {
        let x = CLLocationManager.authorizationStatus()
        switch x {
        case .authorizedWhenInUse:
            locationPermissionLabel.text = "Allowed when in use"
        case .notDetermined:
            locationPermissionLabel.text = "notDetermined"
        case .restricted:
            locationPermissionLabel.text = "restricted"
        case .denied:
            locationPermissionLabel.text = "denied"
        case .authorizedAlways:
            locationPermissionLabel.text = "authorizedAlways"
        @unknown default:
            locationPermissionLabel.text = "unknown default"
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        // request authorization for the corelocation data
        self.updatePermissionStatus()

        let corelocationsub = coreLocationProxy
            .publisher
            .print("headingSubscriber")
            .receive(on: RunLoop.main)
            .sink { someValue in  // 5
                self.headingLabel.text = String(someValue.trueHeading)
            }
        headingSubscriber = AnyCancellable(corelocationsub)
    }

}
  1. CoreLocation 的特点之一是要向用户请求访问数据的许可。 启动此请求的 API 将立即返回,但即使用户允许或拒绝请求,它并不提供任何详细信息。 CLLocationManager 类包括信息,并在想要获取信息时将其作为类方法暴露给外部,但未提供任何信息来了解用户何时或是否响应了请求。 由于操作不提供任何返回信息,我们将整数提供给管道作为数据,主要表示已发出请求。
  2. 由于没有明确的方法来判断用户何时会授予权限,但权限是持久的,因此在尝试获取数据之前,我们简单地使用了 delay 操作符。 此使用只会将值的传递延迟两秒钟。
  3. 延迟后,我们调用类方法,并尝试根据当前提供的状态的结果更新界面中的信息。
  4. 由于 CoreLocation 需要调用方法来明确启用或禁用数据,因此将我们发布者 proxy 的方法连接到了一个 UISwitchIBAction 开关上。
  5. 方位数据在本 sink 订阅者中接收,在此示例中,我们将其写到文本 label 上。

参考

https://heckj.github.io/swiftui-notes/index_zh-CN.html

代码

https://github.com/heckj/swiftui-notes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值