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

goh*_*tis 3 ios swiftui

我正在编写一个 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

sup*_*cio 5

1)关于第一点(应用程序首先打开):可能获得你想要的最好方法是使用DataBindingObservableObjects提取视图之外的逻辑(如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
Run Code Online (Sandbox Code Playgroud)

这样,一旦ViewModel创建,currentValue就会更新。此外,每次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()
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

  • 移除观察者onDisappear(这很常见)
  • 添加观察者一次,检查您是否已经添加了它。

在任何情况下,执行以下操作都是一个好习惯:

deinit {

}
Run Code Online (Sandbox Code Playgroud)

方法并最终移除观察者。

  • @gohnjanotis为了在“View”中拥有可变状态,您应该使用“@State”属性包装器。当您有一些严格属于视图本身的状态时(如本例中的布尔值),您应该编写类似“@State private var addNotificationObserver = true”的内容(在这种情况下始终使用“private”来强制您是管理一些仅属于视图的状态,并且不打算被其他一些实体访问)。 (2认同)