我该如何在SwiftUI应用程序中触发网络调用以在应用程序打开时刷新数据?

3
我正在编写一个SwiftUI应用程序,并希望定期从服务器刷新数据:
  • 当应用程序首次打开时
  • 如果应用程序进入前台且数据在过去的5分钟内未更新
以下是我目前已经编写的代码。
在SwiftUI应用程序中,触发此更新代码的最佳方法是什么? 在onAppear中添加观察者以触发更新当应用程序进入前台时是否是一种好的实践?(这是应用程序中唯一的视图)
class InfoStore {

    var lastValueCheck: Date = .distantPast
}

struct ContentView : View {

    var infoStore: InfoStore

    private func updateValueFromServer() {

        // request updated value from the server

        // if the request is successful, store the new value

        currentValue = 500
        UserDefaults.cachedValue = 500
        // hardcoded for this example

        infoStore.lastValueCheck = Date()
    }

    private func updateValueIfOld() {

        let fiveMinutesAgo: Date = Date(timeIntervalSinceNow: (-5 * 60))

        if infoStore.lastValueCheck < fiveMinutesAgo {
            updateValueFromServer()
        }
    }

    @State var currentValue: Int = 100

    var body: some View {
        Text("\(currentValue)")
        .font(.largeTitle)
        .onAppear {
                NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification,
                                                       object: nil,
                                                       queue: .main) { (notification) in
                    self.updateValueIfOld()
                }
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let cachedValue = "cachedValue"
    }

    static var cachedValue: Int {
        get {
            return standard.value(forKey: Keys.cachedValue) as? Int ?? 0
        }
        set {
            standard.set(newValue, forKey: Keys.cachedValue)
        }
    }
}

1
我编辑了我的回答以涵盖你的第二个问题。 - superpuccio
1个回答

5

1) 关于第一点(应用程序首次打开):可能获得您想要的最好方式是使用 DataBindingObservableObject 将逻辑提取到 View 之外(如 MVVM 建议)。为了向您展示我的意思,我尽可能少地更改了您的代码:

import SwiftUI

class ViewModel: ObservableObject {
    @Published var currentValue = -1
    private var lastValueCheck = Date.distantPast

    init() {
        updateValueFromServer()
    }

    func updateValueIfOld() {
        let fiveMinutesAgo: Date = Date(timeIntervalSinceNow: (-5 * 60))

        if lastValueCheck < fiveMinutesAgo {
            updateValueFromServer()
        }
    }

    private func updateValueFromServer() {
        // request updated value from the server

        // if the request is successful, store the new value

        currentValue = 500
        UserDefaults.cachedValue = 500
        // hardcoded for this example

        lastValueCheck = Date()
    }
}

struct ContentView : View {

    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Text("\(viewModel.currentValue)")
        .font(.largeTitle)
        .onAppear {
                NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification,
                                                       object: nil,
                                                       queue: .main) { (notification) in
                                                        self.viewModel.updateValueIfOld()
                }
        }
    }
}

extension UserDefaults {

    private struct Keys {
        static let cachedValue = "cachedValue"
    }

    static var cachedValue: Int {
        get {
            return standard.value(forKey: Keys.cachedValue) as? Int ?? 0
        }
        set {
            standard.set(newValue, forKey: Keys.cachedValue)
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(viewModel: ViewModel())
    }
}
#endif

这样,一旦创建了ViewModelcurrentValue就会被更新。此外,每当通过服务器调用更改currentValue时,UI会自动重新创建。请注意,您必须按照以下方式修改sceneDelegate

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView(viewModel: ViewModel()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }

2) 关于第二点(应用程序进入前台):您需要小心处理,因为您正在多次注册观察者(每次 onAppear 被触发时)。根据您的需求,您应该决定:

  • 移除观察者 onDisappear (这非常频繁)
  • 仅添加观察者一次,并检查是否已经添加。

无论如何,实现以下是一个好习惯:

deinit {

}

方法并最终删除观察者。


我喜欢检查观察者是否已经添加并仅添加一次的方法。为此,我在ViewModel中创建了一个名为foregroundObserverAdded的变量。起初,我尝试在ContentView中创建它,但是当我尝试设置它(在设置观察者之后)时,我遇到了一个错误,因为ContentView是一个结构体。把它放在ViewModel中来解决这个问题,这看起来像一个好的解决方案吗? - gohnjanotis
2
@gohnjanotis 为了在View中拥有可变状态,您应该使用@State属性包装器。当您有一些状态严格属于视图本身时(就像这种布尔值的情况),您应该编写类似于@State private var addNotificationObserver = true的内容(在这种情况下始终使用private来强制执行您正在管理仅属于视图的某些状态,并且不打算由其他实体访问)。 - superpuccio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接