You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM架构下@Observable类引用的状态管理困惑咨询

SwiftUI MVVM中@Observable引用外部状态的解决方案

问题背景

在SwiftUI的MVVM架构中,当@Observable类引用外部对象状态时,容易出现状态变化无法被正确观察的问题。比如开发需要位置授权的应用时,单例LocationManager维护授权状态,但ViewModel中的关联属性无法同步更新:

初始实现代码

LocationManager:

final class LocationManager: NSObject {
    static let shared = LocationManager()

    private let locationManager = CLLocationManager()
    var isAuthorized = false
    
    override init() {
        super.init()
        locationManager.delegate = self
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ lm: CLLocationManager) {
        switch locationManager.authorizationStatus {
        case .authorizedAlways, .authorizedWhenInUse:
            isAuthorized = true
        default:
            isAuthorized = false
        }
    }
}

ContentView及ViewModel:

struct ContentView: View {
    @State var viewModel = ViewModel()
    
    var body: some View {
        SomeOtherView()
            .sheet(isPresented: $viewModel.isNotAuthorized) {
                Text("Please authorize location services")
                    .interactiveDismissDisabled()
            }
    }
    
    @Observable class ViewModel {
        var isNotAuthorized = !LocationManager.shared.isAuthorized
    }
}

上述代码中,viewModel.isNotAuthorized仅在初始化时赋值,后续LocationManager.isAuthorized变化无法触发视图更新。尝试的几种方案均有顾虑:

  • 将LocationManager标记为@Observable,不确定是否符合编码规范;
  • 视图直接依赖LocationManager,会导致状态管理混乱;
  • 把状态推到各ViewModel用回调实现,模型无法持有统一状态,不切实际。

推荐解决方案:让LocationManager支持@Observable

直接将LocationManager改为@Observable类,使其状态变化能被SwiftUI自动追踪,同时严格遵循MVVM职责划分:

修改后的LocationManager

@Observable final class LocationManager: NSObject {
    static let shared = LocationManager()

    private let locationManager = CLLocationManager()
    var isAuthorized = false
    
    override init() {
        super.init()
        locationManager.delegate = self
        // 初始化时同步当前授权状态
        updateAuthorizationStatus()
    }
    
    private func updateAuthorizationStatus() {
        switch locationManager.authorizationStatus {
        case .authorizedAlways, .authorizedWhenInUse:
            isAuthorized = true
        default:
            isAuthorized = false
        }
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ lm: CLLocationManager) {
        updateAuthorizationStatus()
    }
}

优化后的ViewModel与视图

struct ContentView: View {
    @State var viewModel = ViewModel()
    
    var body: some View {
        SomeOtherView()
            .sheet(isPresented: $viewModel.isNotAuthorized) {
                Text("请授权位置服务")
                    .interactiveDismissDisabled()
                    .onTapGesture {
                        viewModel.requestAuthorization()
                    }
            }
    }
    
    @Observable class ViewModel {
        private let locationManager = LocationManager.shared
        
        // 计算属性自动同步外部状态
        var isNotAuthorized: Bool {
            !locationManager.isAuthorized
        }
        
        // 业务逻辑封装在ViewModel中
        func requestAuthorization() {
            locationManager.locationManager.requestWhenInUseAuthorization()
        }
    }
}

方案优势

  • 单一可信源:所有授权状态由LocationManager统一维护,避免多副本不一致;
  • 清晰职责划分:LocationManager负责状态维护,ViewModel负责状态转换与业务逻辑,视图仅做UI渲染;
  • 自动状态追踪:借助@Observable特性,无需手动订阅或回调,SwiftUI自动处理依赖更新。

兼容Combine的替代方案

如果项目仍使用Combine,可以通过PassthroughSubject实现状态发布与订阅:

修改后的LocationManager

import Combine

final class LocationManager: NSObject {
    static let shared = LocationManager()

    private let locationManager = CLLocationManager()
    var isAuthorized = false {
        didSet {
            authorizationSubject.send(isAuthorized)
        }
    }
    let authorizationSubject = PassthroughSubject<Bool, Never>()
    
    override init() {
        super.init()
        locationManager.delegate = self
        updateAuthorizationStatus()
    }
    
    private func updateAuthorizationStatus() {
        switch locationManager.authorizationStatus {
        case .authorizedAlways, .authorizedWhenInUse:
            isAuthorized = true
        default:
            isAuthorized = false
        }
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManagerDidChangeAuthorization(_ lm: CLLocationManager) {
        updateAuthorizationStatus()
    }
}

ViewModel订阅状态

@Observable class ViewModel {
    private let locationManager = LocationManager.shared
    private var cancellables = Set<AnyCancellable>()
    
    var isNotAuthorized = false
    
    init() {
        // 订阅状态变化并更新自身属性
        locationManager.authorizationSubject
            .map { !$0 }
            .assign(to: \.isNotAuthorized, on: self)
            .store(in: &cancellables)
        
        // 初始化同步初始状态
        isNotAuthorized = !locationManager.isAuthorized
    }
    
    func requestAuthorization() {
        locationManager.locationManager.requestWhenInUseAuthorization()
    }
}

内容的提问来源于stack exchange,提问作者bli00

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:07:06