如何存储在委托 class 中收到的值,在外部委托中?

How to store value, received in delegate class, inside external one?

有一个 BLEManager class,它负责扫描、连接和接收来自低功耗蓝牙 (BLE) 设备的数据。看起来像这样:

class BLEManager: ObservableObject, OtherProtocols {
    private var myCentral: CBCentralManager!
    @Published var data = 0.0

    override init() {
        super.init()

        myCentral = CBCentralManager(delegate: self, queue: nil)
        myCentral.delegate = self
    }

    // ...some functions that scan, establish connection, etc.

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        // here I receive raw value, handle it and get a data to work with
        
        data = 10.0 // imagine it's the data received from BLE device 
    } 
}

现在 “要使用的数据” 存储在此 class 中。我想以这种方式移动这些数据,所以当前的 class (BLEManager) 只负责 BLE 逻辑,数据与其他用户数据一起存储。 Swift可以吗?

P.s。我是 Swift 的新手。有JS经验。

已编辑

在当前情况下,BLEmanager 从一个特定的外设接收数据。需要明确的是,数据代表人体重量。除此之外,还有一个包含人类生物特征数据(年龄、身高、性别)的结构。归根结底,生物特征数据 + 来自设备的数据(体重)密切相关,并用于相同的计算。

结果

我能够实施 Cristik 的方法。唯一的区别是,在我的例子中,订阅发生在视图的 .onAppear() 修饰符中,而不是在 class init 上,正如他所描述的那样。将发布者传递给 class.

时遇到问题

如果您打算连接到单个设备,我会保持这非常简单,让 BLEManager 在数据传入时将其传递给委托。然后委托将负责决定如何处理数据.如果您不熟悉这种模式,Swift by Sundell 对委托有很好的介绍。

您将创建一个单独的对象,一个“DataManager”或任何您喜欢的名称,它将成为 BLEManager 的委托(并且将拥有 BLEManager)。每当有新数据进来时,调用 bleManager(_:didReceiveWeight:)bleManager(_:didReceiveBiometricData:).

等方法

这将从管理数据和执行计算中分离出从设备获取数据的过程,我认为这是一个有价值的目标。

如果这是一个“真正的”项目,并且您刚刚开始,我强烈推荐这种模式。很好理解,很多年前就有几十篇关于它的博客 post。它易于理解和实施。唯一 更容易 实施的模式是 post Notifications,来自 BLEManager。

另一方面,如果这更像是一种探索,并且您想跳入 Swift 的深处和未来(并将自己限制在 iOS 15),您还可以查看 AsyncStream 以在 AsyncSequence 中出现新值时发出新值。这最终可能是“Swifty”的方式来做到这一点。但这是一个更陡峭的学习曲线,而且工具还没有完全开发,而且还没有人 真正 知道如何使用它(甚至 Apple 也不知道;它太新了)。如果进入技术底层让您兴奋,那么这是每个人现在都在探索的领域。

但是,如果您只想让这件事发挥作用,或者您现在想专注于学习 Swift 而不是学习“Swift 的未来”,我会使用委托。

I'd like to move this data in such a way, so the current class (BLEManager) is responsible for the BLE logic only, and data is stored together with other user data

这是一个很好的心态,因为目前您的 BLEManager 打破了单一职责原则,即承担多项职责。 ObservedObject 部分是 SwiftUI 特定的东西,因此从 class.

中提取出来是有意义的

现在,在实现方面,您可以做的第一步是将 data 属性 转换为发布者。这将允许客户端连接到数据流,并允许您在应用程序的其余部分循环发布者而不是 BLEManager class。

import Combine

class BLEManager: OtherProtocols {
    // you can use `Error` instead of `Never` if you also want to
    // report errors which make the stream come to an end
    var dataPublisher: AnyPublisher<Int, Never> { _dataPublisher.eraseToAnyPublisher() }

    private var myCentral: CBCentralManager!

    // hiding/encapsulating the true nature of the publisher
    private var _dataPublisher = PassthroughSubject<Int, Never>()

    // ...

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        _dataPublisher.send(10.0) // imagine it's the data received from BLE device 
    } 

这样任何对接收 BLE 数据感兴趣的人都可以订阅该发布者。

现在,在接收方,假设您的 SwiftUI 视图还需要 ObservableObject,您可以编写以下内容:

class ViewModel: ObservableObject {
    @Published var data: Int = 0
    
    init(dataPublisher: AnyPublisher<Int, Never>) {
        // automatically updates `data` when new values arrive
        dataPublisher.assign(to: &$data)
    }
}

如果您不使用 SwiftUI(由于 ObservableObject 一致性,我假设您使用),那么您可以 sink 到同一发布者以接收数据。


无论是 SwiftUI 还是 UIKit,一旦你在某处实例化了 BLEManager,你就可以将它从应用程序的其余部分隐藏起来,并且仍然提供订阅 BLE 数据的方法,通过循环发布者.这也有助于分离应用程序其余部分的关注点。