经过通过Demo工程不停的测试,终于尝试出来两种版本可以解决问题,一种通过@ObservedObject的方式可以解决问题,另外通过@StateObject解决问题。但是不管通过@ObservedObject还是@StateObject方式,都需要将需要修改的对象用@Published声明。
swift
class RootModel: ObservableObject {
static let root = RootModel()
@Published var model:Model = Model()
}
class Model: ObservableObject {
@Published var text:String = ""
@Published var isOpen:Bool = true {
didSet {
text = "你能发现我了,恭喜你!"
}
}
}
下面讲述一下通过@ObservedObject的方式来实现。
swift
@main
struct ExampleApp: App {
@StateObject var model = RootModel()
var body: some Scene {
WindowGroup {
VStack {
ContentView(model: model.model)
Button("tap") {
model.model = Model()
}
}
}
}
}
swift
struct ContentView: View {
@ObservedObject var model:Model
var body: some View {
VStack {
Text(model.text)
Toggle("是否显示", isOn: $model.isOpen)
}
}
}

当是我运行看到效果达到的时候,我并没有满足当前的解决方案,我觉得通过传递参数这一种有点复杂,并不是我们想要的,后来经过尝试了很多次,终于发现了另外的一种。
通过@StateObject达成效果
swift
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
VStack {
ContentView()
Button("tap") {
RootModel.root.model = Model()
}
}
}
}
}
swift
struct ContentView: View {
@StateObject var model = RootModel.root
var body: some View {
VStack {
Text(model.model.text)
Toggle("是否显示", isOn: $model.model.isOpen)
}
}
}
通过上述的代码,我们一样完成了功能。不过的是我们的RootModel需要做成单例模式,不过这个没关系,正好符合我们的需求。
不过我觉得第二种实现起来更加的方便,不需要将参数传来传去的。那么我们就用第二种方法改造上一章节的问题。
改造 UserConfig 的生成规则
第一步 修改 AppConfig 中的 userConfig 从 Optional 修改为 No Optional
swift
var userConfig:UserConfig
这样我们在使用UserConfig中的参数@Published时候不会因为语法报错,当用户没有登录使用默认的值,也是符合正常的业务逻辑。
第二步 修改 getUserConfig 方法的逻辑

swift
private func getUserConfig() -> UserConfig {
/// 流程图地址 
/// 如果服务器地址为空 或者 当前登录用户不存在 则返回 [server = "" user = 0]的默认配置
let defaultUserConfig = UserConfig(server: "", user: "0")
guard !currentAppServer.isEmpty else { return defaultUserConfig }
guard let currentUserId = try? UserManager.EmployeeNo(currentUserId).value else { return defaultUserConfig }
return UserConfig(server: currentAppServer, user: currentUserId)
}
但是这个方式还是存在一些问题,可能还存在下面的情况

既然是用户配置,自然和用户强相关的,没有服务器地址用户就不能登录,没有登录就不存在用户ID,所以获取不到统一用一套新的用户配置是可以的,当在已经登录情况下,不存在服务器地址和用户ID是错误的,是不允许存在的。
为了保障我们获取UserConfig的逻辑的严谨性,我们按照最新的逻辑图进行修改代码。
swift
class AppConfig: ObservableObject {
...
/// 流程图地址 https://gitee.com/joser_zhang/upic/raw/master/uPic/202112151044218.png
private func getUserConfig() -> UserConfig {
guard !currentAppServer.isEmpty else {
/// 如果服务器为空 则创建 [server = ""] [user = 0]的用户配置
return UserConfig(server: "", user: "0")
}
guard let currentUserId = try? UserManager.EmployeeNo(currentUserId).value else {
/// 如果服务器不为空 当前不存在登录用户的ID 则创建 [server = "xxx"] [user = "0"]的用户配置
return UserConfig(server: currentAppServer, user: "0")
}
/// 如果服务器存在 存在登录用户的ID 就返回[server = "xxx"][user = "xxx"]的用户配置
/// 里面是否重新创建配置还是读取本地已经存在配置 交给 `UserConfig`处理
return UserConfig(server: currentAppServer, user: currentUserId)
}
}
第三步 修改 AppConfig 初始化
swift
fileprivate extension Notification.Name {
...
static let currentServerChanged = Notification.Name("currentServerChanged")
}
swift
class AppConfig: ObservableObject {
...
/// 当前 App 的服务器地址
@AppStorage("currentAppServer")
var currentAppServer:String = "" {
didSet {
NotificationCenter.default.post(name: .currentServerChanged, object: nil)
}
}
...
private var cancellabelSet:Set<AnyCancellable> = []
init() {
...
/// 监听 `currentAppServer` 的变化重新生成 `UserConfig`
NotificationCenter.default.publisher(for: .currentServerChanged, object: nil)
.sink { [weak self] no in
/// 监听到`currentAppServer`改变,重新生成`UserConfig`
guard let self = self else {return}
self.userConfig = self.getUserConfig()
}
.store(in: &cancellabelSet)
}
...
}
修改是否登录逻辑
为了可以确保可以在用户登录之后拿到UserConfig没有问题,我们需要修改一下isLogin的逻辑。我们假设一下我们我们不修改会造成什么的危害?

红色箭头的逻辑是有问题的,因为覆盖安装,旧版本已经登录情况下,用户操作的所有配置都保存在默认配置下面,是存在问题的。
为了解决旧版本已经登录的情况,我们就修改isLogin的逻辑,让旧版本已经登录的用户保持未登录的状态。

我们将之前通过判断gatewayUserName换成了employeeNO,不但兼容了老版本,而且新版本后续登录也不会出问题。
1 新增 employeeNo 是否来源于缓存字段替换 isGatewayUserNameFromCache
swift
class AppConfig: ObservableObject {
...
/// `employeeNo` 是否来源于缓存 默认来源于缓存
var isEmplyeeNoFromCache:Bool = true
...
}
2 用户主动登录之后 修改 isEmplyeeNoFromCache = false
swift
struct UserManager {
...
/// 进行登录
func login() {
AppConfig.share.isEmplyeeNoFromCache = false
...
}
}
3 修改入口 isNeedLogin 代码
swift
struct Win_App: App {
@StateObject private var appConfig:AppConfig = AppConfig.share
...
private var isNeedLogin:Bool {
/// 如果 `employeeNo` 不存在 则需要进行登录
guard isExitUserId else { return true}
/// 如果 `employeeNo` 存在 并且 `isEmplyeeNoFromCache = false` 代表是刚刚登录的 则不需要登录
guard appConfig.isEmplyeeNoFromCache else { return false }
/// 此时 `employeeNo`已经存在 假设是全新创建`UserConfig`默认`isAutoLogin = false`也是需要进行登录操作的
return !appConfig.userConfig.isAutoLogin
}
private var isExitUserId:Bool {
guard let _ = try? UserManager.EmployeeNo(appConfig.currentUserId).value else {
return false
}
return true
}
}
修复工程报错
因为我们将所有存放在AppConfig的信息转移到UserConfig中,很多地方出现了报错,经过上面修改逻辑,我们拿着AppConfig.UserConfig修复工程中出现的错误。
修复 Api
swift
class Api: API {
...
static var defaultHeadersConfig: ((inout HTTPHeaders) -> Void)? {
return { headers in
if let gatewayUserName = AppConfig.share.userConfig.gatewayUserName {
...
}
if let currentFactoryCode = AppConfig.share.userConfig.currentFactoryCode {
...
}
}
}
}
修复 HomePageViewModel
swift
class HomePageViewModel: BaseViewModel {
...
@Published var currentFactory:FactoryListResponseModel = FactoryListResponseModel(factoryCode: nil,
factoryName: nil) {
didSet {
AppConfig.share.userConfig.currentFactoryCode = currentFactory.factoryCode
}
}
...
/// 查找保存的工厂代码对应最新工厂列表的模型
private func findFactory() -> FactoryListResponseModel? {
return factoryList.first { model in
guard let currentFactoryCode = AppConfig.share.userConfig.currentFactoryCode else {return false}
...
}
}
}
修复 MyPage
swift
struct MyPage: View {
...
@StateObject private var appConfig = AppConfig.share
...
private func userNameCell() -> some View {
MyDetailStyle1CellContentView(title: "姓名",
detail: AppConfig.share.userConfig.userInfoModel?.userName ?? "")
}
...
private func autoLoginCell() -> some View {
MyCellContentView(title: "自动登录") {
Toggle("", isOn: $appConfig.userConfig.isAutoLogin)
}
}
...
private func logoutButton() -> some View {
Button {
appConfig.userConfig.gatewayUserName = nil
...
} label: {
...
}
}
/// 点击了产线
private func didClickProductLine() {
guard let _ = appConfig.userConfig.workShopCode else {
...
return
}
...
}
...
}
修复 AppConfig 报错
swift
class AppConfig: ObservableObject {
...
/// 当前 App 的服务器地址
@AppStorage("currentAppServer")
var currentAppServer:String = "" {
...
}
...
var userConfig:UserConfig
...
init() {
/// ❌ 'self' used in property access 'currentAppServer' before all stored properties are initialized
if currentAppServer.isEmpty {
...
}
/// ❌ 'self' used in method call 'getUserConfig' before all stored properties are initialized
/// 初始化 UserConfig
self.userConfig = getUserConfig()
...
}
/// 流程图地址 https://gitee.com/joser_zhang/upic/raw/master/uPic/202112151044218.png
private func getUserConfig() -> UserConfig {
...
}
}
分别存在两个错误
currentAppServer在userConfig之前使用,因为调用方法和变量默认省去了self.,所以我们需要在AppConfig初始化完毕才能使用currentAppServer- 我们在
AppConfig初始化之前调用了方法getUserConfig
1 将 getUserConfig 方法修改为类方法
swift
/// 流程图地址 https://gitee.com/joser_zhang/upic/raw/master/uPic/202112151044218.png
/// 根据提供的服务器地址 和当前登录用户的唯一ID 查询本地已经存在的用户配置 或者重新生成新的用户配置
/// - Parameters:
/// - server: 服务器地址
/// - userId: 当前登录用户的唯一ID
/// - Returns: 当前用户的配置
private static func getUserConfig(from server:String, userId:String?) -> UserConfig {
guard !server.isEmpty else {
/// 如果服务器为空 则创建 [server = ""] [user = 0]的用户配置
return UserConfig(server: "", user: "0")
}
guard let currentUserId = try? UserManager.EmployeeNo(userId).value else {
/// 如果服务器不为空 当前不存在登录用户的ID 则创建 [server = "xxx"] [user = "0"]的用户配置
return UserConfig(server: server, user: "0")
}
/// 如果服务器存在 存在登录用户的ID 就返回[server = "xxx"][user = "xxx"]的用户配置
/// 里面是否重新创建配置还是读取本地已经存在配置 交给 `UserConfig`处理
return UserConfig(server: server, user: currentUserId)
}
2 在 AppConfig 初始化之前获取 server 和 userId 的值
swift
let server = _currentAppServer.wrappedValue
let userId = _currentUserId.wrappedValue
3 调整 currentAppServer 赋值和 userConfig 初始化的位置
swift
class AppConfig: ObservableObject {
...
init() {
...
/// 初始化 UserConfig
self.userConfig = AppConfig.getUserConfig(from: server, userId: userId)
if currentAppServer.isEmpty {
...
}
...
}
...
}
4 修复 AppConfig 其他报错
swift
class AppConfig: ObservableObject {
...
init() {
...
/// 监听 currentUserId 的变化
/// `@AppStorage` 是无法进行监听的 因此这里采用 `Notification`
NotificationCenter.default.publisher(for: .currentUserIdChanged, object: nil)
.sink {[weak self] no in
...
self.userConfig = AppConfig.getUserConfig(from: self.currentAppServer, userId: self.currentUserId)
}
...
/// 监听 `currentAppServer` 的变化重新生成 `UserConfig`
NotificationCenter.default.publisher(for: .currentServerChanged, object: nil)
.sink { [weak self] no in
...
self.userConfig = AppConfig.getUserConfig(from: self.currentAppServer, userId: self.currentUserId)
}
...
}
...
}
修复 MyPageViewModel
swift
class MyPageViewModel: BaseViewModel {
...
/// 当前选中的车间
@Published var currentWorkshop:GetAllWorkshopResponse? {
didSet {
AppConfig.share.userConfig.workShopCode = currentWorkshop?.workshopCode
}
}
...
/// 当前选中产线的模型
@Published var currentProductLine:GetAllProductLineApiResponse? {
didSet {
AppConfig.share.userConfig.productLineCode = currentProductLine?.code
}
}
...
/// 当前选中的仓库
@Published var currentStoreHouse:GetAllStoreHouseApiResponse? {
didSet {
AppConfig.share.userConfig.storeHouseCode = currentStoreHouse?.code
}
}
override init() {
...
workshopCancellabel = AppConfig.share.userConfig.$workShopCode.sink {[weak self] value in
...
}
}
...
private func getAllWorkShop() async {
...
if let workShopCode = AppConfig.share.userConfig.workShopCode {
...
guard let _ = index else {
/// 如果查询不到 意味着之前选中的数据已经不存在 则默认第一个
AppConfig.share.userConfig.workShopCode = workShops.first?.workshopCode
return
}
AppConfig.share.userConfig.workShopCode = workShopCode
} else {
/// 如果之前没有选中的车间 则默认第一个
AppConfig.share.userConfig.workShopCode = workShops.first?.workshopCode
}
currentWorkshop = data.first(where: { response in
guard let configCode = AppConfig.share.userConfig.workShopCode else {return false}
...
})
}
/// 获取车间下面的所有产线
private func getAllProductLine() async {
guard let workShopCode = AppConfig.share.userConfig.workShopCode else {
return
}
...
/// 是否存在之前选中保存的产线code
if let productLineCode = AppConfig.share.userConfig.productLineCode {
...
} else {
...
}
}
func getAllStoreHouse() async {
...
/// 是否存在之前保存过的仓库
if let storeHouseCode = AppConfig.share.userConfig.storeHouseCode {
...
} else {
...
}
}
...
/// 当前选中车间的名称
func currentWorkShopName() -> String? {
return workShops.first { response in
guard let workShopCode = AppConfig.share.userConfig.workShopCode else {return false}
...
}?.name
}
...
}
修复 UserManager
1 修复报错
swift
struct UserManager {
...
/// 进行登录
func login() {
...
AppConfig.share.userConfig.gatewayUserName = gatewayUserName
AppConfig.share.userConfig.userInfoModel = user
...
}
}
在修复上述代码的时候,我们发现了一个问题。
swift
/// 进行登录
func login() {
AppConfig.share.isEmplyeeNoFromCache = false
/// 设置`gatewayUserName`和`userInfoModel`值
...
/// 设置`currentUserId`重新创建一个新的`UserConfig`
AppConfig.share.currentUserId = employeeNo
}
系统监听到currentUserId变动,重新创建新的UserConfig。
2 调整 设置 gatewayUserName 和 userInfoModel 值位置
swift
/// 进行登录
func login() {
...
AppConfig.share.currentUserId = employeeNo
AppConfig.share.userConfig.gatewayUserName = gatewayUserName
AppConfig.share.userConfig.userInfoModel = user
}

运行登录,看起来十分的正常。但是我们操作退出的时候发现了竟然无法退出。

修复退出登录异常
swift
struct MyPage: View {
...
private func logoutButton() -> some View {
Button {
appConfig.userConfig.gatewayUserName = nil
appConfig.currentTabIndex = 0
} label: {
...
}
}
...
}
无法退出的原因在于我们判断登录调整为employeeNo,但是退出登录清空的是gatewayUserName。
1 修复退出失败
swift
struct MyPage: View {
...
private func logoutButton() -> some View {
Button {
appConfig.currentUserId = nil
...
} label: {
...
}
}
...
}
2 UserManager 新增退出方法
swift
struct UserManager {
...
/// 退出登录
static func logout() {
AppConfig.share.currentUserId = nil
AppConfig.share.currentTabIndex = 0
}
}
swift
struct MyPage: View {
...
private func logoutButton() -> some View {
Button {
UserManager.logout()
} label: {
...
}
}
...
}
