健身APP开发:如何在ViewController间传递连续心率数据流?
首先得说,你想解耦蓝牙数据获取和显示模块的思路非常正确——把蓝牙Manager和UI逻辑混在同一个VC里确实会让代码越来越难维护。针对连续数据流的传递,我们有几个比prepareForSegue或单次Notification更合适的方案,同时也能解决你提到的Container View和UIPageViewController之间的数据传递问题:
方案1:自定义Delegate协议(适配连续数据流)
Delegate模式可不是只能用来传递单次数据,我们可以把它改成“持续回调”的形式,完美适配心率这种连续数据流:
步骤1:抽离蓝牙逻辑并定义协议
先把蓝牙相关的代码从VC里单独拎出来,做成一个独立的Manager类(这步很关键,比放在VC里解耦度高太多):
protocol HeartRateDataDelegate: AnyObject { func didReceiveHeartRateUpdate(_ heartRate: Int) } class BluetoothHeartRateManager { weak var delegate: HeartRateDataDelegate? func startScanning() { // 实际场景中,这里替换成蓝牙传感器的连续回调逻辑 Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in let simulatedHeartRate = Int.random(in: 60...180) self?.delegate?.didReceiveHeartRateUpdate(simulatedHeartRate) } } }
步骤2:在显示端VC实现代理
不管是Container View里的子VC,还是UIPageViewController中的页面VC,都可以实现这个协议来接收连续数据:
class HeartRateDisplayVC: UIViewController, HeartRateDataDelegate { @IBOutlet weak var heartRateLabel: UILabel! let heartRateManager = BluetoothHeartRateManager() override func viewDidLoad() { super.viewDidLoad() heartRateManager.delegate = self heartRateManager.startScanning() } func didReceiveHeartRateUpdate(_ heartRate: Int) { // 切到主线程更新UI(蓝牙数据通常在后台线程返回) DispatchQueue.main.async { self.heartRateLabel.text = "\(heartRate) BPM" } } }
针对Container View的传递
如果蓝牙Manager在Container的父VC里,你可以在父VC中拿到Container的子VC实例,直接设置代理或传递Manager:
class ParentVC: UIViewController { let heartRateManager = BluetoothHeartRateManager() private var childDisplayVC: HeartRateDisplayVC? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "ContainerSegue" { childDisplayVC = segue.destination as? HeartRateDisplayVC childDisplayVC?.heartRateManager = heartRateManager childDisplayVC?.heartRateManager.delegate = childDisplayVC } } }
针对UIPageViewController的传递
UIPageViewController需要先获取当前显示的页面VC,再传递数据:
class MyPageViewController: UIPageViewController { let heartRateManager = BluetoothHeartRateManager() override func viewDidLoad() { super.viewDidLoad() heartRateManager.delegate = self heartRateManager.startScanning() } } extension MyPageViewController: HeartRateDataDelegate { func didReceiveHeartRateUpdate(_ heartRate: Int) { // 获取当前活跃的页面VC guard let currentVC = viewControllers?.first as? HeartRateDisplayVC else { return } currentVC.updateHeartRate(heartRate) } } // 在页面VC中添加更新方法 extension HeartRateDisplayVC { func updateHeartRate(_ heartRate: Int) { DispatchQueue.main.async { self.heartRateLabel.text = "\(heartRate) BPM" } } }
方案2:使用Combine框架(iOS 13+,推荐)
Combine天生就是为处理连续数据流设计的,用它来传递心率数据非常优雅,几乎不需要额外的耦合:
步骤1:在蓝牙Manager中定义数据流
import Combine class BluetoothHeartRateManager { // 定义一个PassthroughSubject,用来发射心率数据(Never表示不会发射错误) let heartRatePublisher = PassthroughSubject<Int, Never>() func startScanning() { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in let simulatedHeartRate = Int.random(in: 60...180) self?.heartRatePublisher.send(simulatedHeartRate) } } }
步骤2:在显示端订阅数据流
class HeartRateDisplayVC: UIViewController { @IBOutlet weak var heartRateLabel: UILabel! let heartRateManager = BluetoothHeartRateManager() // 用来存储订阅,防止被销毁 private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() // 订阅数据流,自动切换到主线程更新UI heartRateManager.heartRatePublisher .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] heartRate in self?.heartRateLabel.text = "\(heartRate) BPM" }) .store(in: &cancellables) heartRateManager.startScanning() } }
这种方式的好处是,不管是Container View还是UIPageViewController里的VC,只要能拿到BluetoothHeartRateManager的实例,就能订阅数据流,不需要繁琐的代理设置,Combine还会帮你自动处理线程切换。
方案3:使用NotificationCenter(适配连续流)
虽然你觉得Notification是针对单次数据,但其实它也能用来发送连续数据流,不过耦合度比前两种方案高,建议只在跨多层VC且不方便用Delegate/Combine的场景下使用:
// 蓝牙Manager中发送通知 class BluetoothHeartRateManager { func startScanning() { Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in let simulatedHeartRate = Int.random(in: 60...180) NotificationCenter.default.post( name: NSNotification.Name(rawValue: "HeartRateUpdate"), object: nil, userInfo: ["heartRate": simulatedHeartRate] ) } } } // 显示端VC监听通知 class HeartRateDisplayVC: UIViewController { @IBOutlet weak var heartRateLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver( self, selector: #selector(handleHeartRateUpdate(_:)), name: NSNotification.Name(rawValue: "HeartRateUpdate"), object: nil ) } @objc func handleHeartRateUpdate(_ notification: Notification) { guard let heartRate = notification.userInfo?["heartRate"] as? Int else { return } heartRateLabel.text = "\(heartRate) BPM" } deinit { NotificationCenter.default.removeObserver(self) } }
额外建议:尽量抽离独立Manager
不管用哪种方案,都强烈建议把蓝牙逻辑做成独立的BluetoothHeartRateManager(可以用单例或者依赖注入传递),这样所有需要心率数据的VC都直接和这个Manager交互,而不是VC之间互相传递数据,能进一步降低耦合度,代码也更易维护。
内容的提问来源于stack exchange,提问作者app4g

