macOS开发中,Manager类等业务逻辑对象是否应使用ObservableObject?
结论很明确:纯业务逻辑的Manager、服务类,应该尽量避免使用ObservableObject、Published、AppStorage这类SwiftUI专属的属性包装器,原因和替代方案如下:
核心逻辑:业务层要和UI层解耦
这些属性包装器是SwiftUI框架的一部分,一旦在纯业务对象中使用,就把业务逻辑和SwiftUI绑定死了。后续如果要切换UI框架(比如部分模块改用UIKit,或者做跨平台开发),业务层的代码就得大面积修改。而且业务逻辑本身的职责是处理业务规则、数据流转,不该依赖UI框架的特性。替代方案:用通用的响应式工具做分层衔接
想要让业务层的变化能通知到ViewModel,完全可以用Foundation自带的Combine框架(不是SwiftUI专属),比如用PassthroughSubject或者CurrentValueSubject发布变化,然后在ViewModel层订阅这些事件,转换成SwiftUI需要的@Published属性。举个简单的例子:
业务层的UserManager:import Combine class UserManager { private(set) var userName: String? private let _userNameUpdated = PassthroughSubject<String?, Never>() // 对外暴露只读的Publisher var userNameUpdated: AnyPublisher<String?, Never> { _userNameUpdated.eraseToAnyPublisher() } func updateUserName(_ newName: String) { // 这里可以加业务校验逻辑,比如长度限制 guard newName.count >= 2 else { return } userName = newName _userNameUpdated.send(newName) } }ViewModel层做转换:
import SwiftUI import Combine class UserViewModel: ObservableObject { @Published var displayName: String? private var cancellables = Set<AnyCancellable>() init(userManager: UserManager) { userManager.userNameUpdated .assign(to: \.displayName, on: self) .store(in: &cancellables) } }关于AppStorage的替代
AppStorage是SwiftUI封装的UserDefaults快捷访问方式,纯业务层应该直接操作UserDefaults(或者封装自己的存储抽象),而不是依赖AppStorage。比如:class SettingsService { private let defaults = UserDefaults.standard private let _themeChanged = PassthroughSubject<String, Never>() var themeChanged: AnyPublisher<String, Never> { _themeChanged.eraseToAnyPublisher() } var activeTheme: String { get { defaults.string(forKey: "activeTheme") ?? "system" } set { defaults.set(newValue, forKey: "activeTheme") _themeChanged.send(newValue) } } }例外情况(不推荐)
如果你的项目100%绑定SwiftUI,且确定未来不会有框架切换、跨平台的需求,偶尔用这些包装器也能跑通代码,但这会牺牲代码的可维护性和可测试性——测试业务逻辑时,你得依赖SwiftUI的运行环境,而不是直接测试纯逻辑。
内容的提问来源于stack exchange,提问作者Damian Dudycz

