使用 Combine 设置发布者,同时满足对非零变量初始值的要求

Using Combine to set up Publishers and while also satisfying requirement for initial values on non-nil variables

我对反应式编程概念比较陌生,我正在尝试构建一个简单的视图模型来更新 @Published Bool 用于保持 UI 使用 Swift [=38] 更新的值=].

此特定模型根据 WatchConnectivity 框架中随时间变化的其他值来设置这些 Bool 值。

尽管这是一个简单的示例并且它正在运行,但我觉得我错过了减少冗余的机会。

具体来说,我感觉很奇怪,我在后面使用Publishers.CombineLatestPublishers.CombineLatest3时重复了用于设置appNotInstalledcomplicationNotInstalled初始值的逻辑.

尽管初始值是通过发布者传递的,所以他们通过 CombineLatest 管道并设置初始值,但随意将发布变量设置为 truefalse,但编译器让我在某处为它们设置初始值。

如果我不设置初始值,我会收到 Variable 'self.appNotInstalled' used before being initialized 错误。

有没有一种方法可以避免设置初始值而不将其设为 nil,或者有其他方法可以避免重复用于确定其值的逻辑?

这是我的工作代码:

class WatchConnectivityModel: ObservableObject {

    // values used to show/hide UI
    @Published var appNotInstalled: Bool
    @Published var complicationNotInstalled: Bool

    private var cancellables: [AnyCancellable] = []

    init() {

        // initialize based on the values of everything at class init
        let activated = WCSession.default.activationState == .activated
        let appInstalled = WCSession.default.isWatchAppInstalled
        let complicationInstalled = WCSession.default.isComplicationEnabled
        appNotInstalled = !(activated && appInstalled)
        complicationNotInstalled = activated && appInstalled && !complicationInstalled

        // set up the publishers for any changes
        let activationStatePublisher = WCSession.default.publisher(for: \.activationState)
        let isWatchAppInstalledPublisher = WCSession.default
            .publisher(for: \.isWatchAppInstalled)
        let isComplicationEnabledPublisher = WCSession.default
            .publisher(for: \.isComplicationEnabled)

        // set up assignment of appNotInstalled for changes
        Publishers.CombineLatest(activationStatePublisher.removeDuplicates(),
                                 isWatchAppInstalledPublisher.removeDuplicates())
            .map { (state, installed) in
                // repeated logic from above
            return !(state == .activated && installed)
        }.receive(on: RunLoop.main)
            .assign(to: \.appNotInstalled, on: self)
            .store(in: &cancellables)

        // set up assignment of complicationNotInstalled for changes
        Publishers.CombineLatest3(activationStatePublisher.removeDuplicates(),
                                  isWatchAppInstalledPublisher.removeDuplicates(),
                                  isComplicationEnabledPublisher.removeDuplicates())
            .map { (state, appInstalled, complicationInstalled) in
                // repeated logic again
                return state == .activated && appInstalled && !complicationInstalled
        }.receive(on: RunLoop.main)
            .assign(to: \.complicationNotInstalled, on: self)
            .store(in: &cancellables)
    }
}

我为每个创建了一个新的初始化结构,它只包含每个的逻辑。每当我设置初始值或更新已发布的变量时,我都会使用它来避免重复逻辑。如果有人对此有更好的解决方案,我愿意接受其他可能性。

import Foundation
import Combine
import WatchConnectivity

class WatchConnectivityModel: ObservableObject {

    // values used to show/hide UI
    @Published var appNotInstalled: Bool
    @Published var complicationNotInstalled: Bool

    private struct AppNotInstalled {

        let value: Bool

        init(_ activationState: WCSessionActivationState,
             _ appInstalled: Bool) {
            value = !(activationState == .activated && appInstalled)
        }
    }

    private struct ComplicationNotInstalled {

        let value: Bool

        init(_ activationState: WCSessionActivationState,
             _ appInstalled: Bool,
             _ complicationInstalled: Bool) {
            value = activationState == .activated && appInstalled && !complicationInstalled
        }
    }

    private var cancellables: [AnyCancellable] = []

    init() {

        // initilize based on the current values
        let state = WCSession.default.activationState
        let appInstalled = WCSession.default.isWatchAppInstalled
        let complicationInstalled = WCSession.default.isComplicationEnabled

        appNotInstalled = AppNotInstalled(state,
                                          appInstalled).value
        complicationNotInstalled = ComplicationNotInstalled(state,
                                                            appInstalled,
                                                            complicationInstalled).value

        // set up the publishers
        let activationStatePublisher = WCSession.default
            .publisher(for: \.activationState)
        let isWatchAppInstalledPublisher = WCSession.default
            .publisher(for: \.isWatchAppInstalled)
        let isComplicationEnabledPublisher = WCSession.default
            .publisher(for: \.isComplicationEnabled)

        // set up assignment of appNotInstalled
        Publishers.CombineLatest(activationStatePublisher.removeDuplicates(),
                                 isWatchAppInstalledPublisher.removeDuplicates())
            .map { (state, installed) in
                return AppNotInstalled(state, installed).value
        }.receive(on: RunLoop.main)
            .assign(to: \.appNotInstalled,
                    on: self)
            .store(in: &cancellables)

        // set up assignment of complicationNotInstalled
        Publishers.CombineLatest3(activationStatePublisher.removeDuplicates(),
                                  isWatchAppInstalledPublisher.removeDuplicates(),
                                  isComplicationEnabledPublisher.removeDuplicates())
            .map { (state, appInstalled, complicationInstalled) in
                return ComplicationNotInstalled(state, appInstalled, complicationInstalled).value
        }.receive(on: RunLoop.main)
            .assign(to: \.complicationNotInstalled,
                    on: self)
            .store(in: &cancellables)
    }
}