第二十八章 重置 ObservableObject 模型数据

0 阅读6分钟

经过通过Demo工程不停的测试,终于尝试出来两种版本可以解决问题,一种通过@ObservedObject的方式可以解决问题,另外通过@StateObject解决问题。但是不管通过@ObservedObject还是@StateObject方式,都需要将需要修改的对象用@Published声明。

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的方式来实现。

@main
struct ExampleApp: App {
    @StateObject var model = RootModel()
    var body: some Scene {
        WindowGroup {
            VStack {
                ContentView(model: model.model)
                Button("tap") {
                    model.model = Model()
                }
            }
        }
    }
}
struct ContentView: View {
    @ObservedObject var model:Model
    var body: some View {
        VStack {
            Text(model.text)
            Toggle("是否显示", isOn: $model.isOpen)
        }
    }
}

6FDE33D6-DF4B-4834-9416-70881C78607D-2852-0000052D9D15DAC7

当是我运行看到效果达到的时候,我并没有满足当前的解决方案,我觉得通过传递参数这一种有点复杂,并不是我们想要的,后来经过尝试了很多次,终于发现了另外的一种。

通过@StateObject达成效果

struct ExampleApp: App {
    var body: some Scene {
        WindowGroup {
            VStack {
                ContentView()
                Button("tap") {
                    RootModel.root.model = Model()
                }
            }
        }
    }
}
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

var userConfig:UserConfig

这样我们在使用UserConfig中的参数@Published时候不会因为语法报错,当用户没有登录使用默认的值,也是符合正常的业务逻辑。

第二步 修改 getUserConfig 方法的逻辑

image-20211215101153452

private func getUserConfig() -> UserConfig {
    /// 流程图地址 ![](https://gitee.com/joser_zhang/upic/raw/master/uPic/202112151011488.png)
    /// 如果服务器地址为空 或者 当前登录用户不存在 则返回 [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)
}

但是这个方式还是存在一些问题,可能还存在下面的情况

image-20211215104451184

既然是用户配置,自然和用户强相关的,没有服务器地址用户就不能登录,没有登录就不存在用户ID,所以获取不到统一用一套新的用户配置是可以的,当在已经登录情况下,不存在服务器地址和用户ID是错误的,是不允许存在的。

为了保障我们获取UserConfig的逻辑的严谨性,我们按照最新的逻辑图进行修改代码。

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 初始化

fileprivate extension Notification.Name {
		...
    static let currentServerChanged = Notification.Name("currentServerChanged")
}
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的逻辑。我们假设一下我们我们不修改会造成什么的危害?

image-20211215113925183

红色箭头的逻辑是有问题的,因为覆盖安装,旧版本已经登录情况下,用户操作的所有配置都保存在默认配置下面,是存在问题的。

为了解决旧版本已经登录的情况,我们就修改isLogin的逻辑,让旧版本已经登录的用户保持未登录的状态。

image-20211215134408761

我们将之前通过判断gatewayUserName换成了employeeNO,不但兼容了老版本,而且新版本后续登录也不会出问题。

1 新增 employeeNo 是否来源于缓存字段替换 isGatewayUserNameFromCache

class AppConfig: ObservableObject {
    ...
    
    /// `employeeNo` 是否来源于缓存 默认来源于缓存
    var isEmplyeeNoFromCache:Bool = true
    
    ...
}

2 用户主动登录之后 修改 isEmplyeeNoFromCache = false

struct UserManager {
    ...
    
    /// 进行登录
    func login() {
        AppConfig.share.isEmplyeeNoFromCache = false
        ...
    }
}

3 修改入口 isNeedLogin 代码

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

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

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

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 报错

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 {
        ...
    }
}

分别存在两个错误

  • currentAppServeruserConfig之前使用,因为调用方法和变量默认省去了self.,所以我们需要在AppConfig初始化完毕才能使用currentAppServer
  • 我们在AppConfig初始化之前调用了方法getUserConfig

1 将 getUserConfig 方法修改为类方法

/// 流程图地址 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 的值

let server = _currentAppServer.wrappedValue
let userId = _currentUserId.wrappedValue

3 调整 currentAppServer 赋值和 userConfig 初始化的位置

class AppConfig: ObservableObject {
    ...
    
    init() {
        ...
        /// 初始化 UserConfig
        self.userConfig = AppConfig.getUserConfig(from: server, userId: userId)
        if currentAppServer.isEmpty {
            ...
        }
        ...
    }
    ...
}

4 修复 AppConfig 其他报错

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

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 修复报错

struct UserManager {
    ...
    
    /// 进行登录
    func login() {
        ...
        AppConfig.share.userConfig.gatewayUserName = gatewayUserName
        AppConfig.share.userConfig.userInfoModel = user
        ...
    }
}

在修复上述代码的时候,我们发现了一个问题。

/// 进行登录
func login() {
    AppConfig.share.isEmplyeeNoFromCache = false
    /// 设置`gatewayUserName`和`userInfoModel`值
    ...
    /// 设置`currentUserId`重新创建一个新的`UserConfig`
    AppConfig.share.currentUserId = employeeNo
}

系统监听到currentUserId变动,重新创建新的UserConfig

2 调整 设置 gatewayUserName 和 userInfoModel 值位置

/// 进行登录
func login() {
    ...
    AppConfig.share.currentUserId = employeeNo
    
    AppConfig.share.userConfig.gatewayUserName = gatewayUserName
    AppConfig.share.userConfig.userInfoModel = user
}

75AB18D4-F91F-4C66-8988-39546467BB58-2852-0000160BA34BBE50

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

A173D48C-59DA-4048-BEF6-9F4DB40921A5-2852-000016289EB2DBED

修复退出登录异常

struct MyPage: View {
    ...
    
    private func logoutButton() -> some View {
        Button {
            appConfig.userConfig.gatewayUserName = nil
            appConfig.currentTabIndex = 0
        } label: {
            ...
        }

    }
    
    ...
}

无法退出的原因在于我们判断登录调整为employeeNo,但是退出登录清空的是gatewayUserName

1 修复退出失败

struct MyPage: View {
    ...
    
    private func logoutButton() -> some View {
        Button {
            appConfig.currentUserId = nil
            ...
        } label: {
            ...
        }

    }
    
    ...
}

2 UserManager 新增退出方法

struct UserManager {
    ...
    
    /// 退出登录
    static func logout() {
        AppConfig.share.currentUserId = nil
        AppConfig.share.currentTabIndex = 0
    }
}
struct MyPage: View {
    ...
    private func logoutButton() -> some View {
        Button {
            UserManager.logout()
        } label: {
            ...
        }

    }
    ...
}

C8D72444-4FAB-4D5F-9FB6-5DE21768F4A5-2852-00001836F3D406E7