
封装了几个网络请求:
class API: ObservableObject { @Published private(set) var isAccessTokenValid = false @AppStorage("Passcode") var passcode: String = "" @AppStorage("AccessToken") var accessToken: String = "" @AppStorage("RefreshToken") var refreshToken: String = "" privite func request() async throws -> (T?, URLResponse?) where T : Decodable { // 省略具体实现 } func request1() async throws -> SomeResponseType {} func request2() async throws -> SomeResponseType {} func request3() async throws -> SomeResponseType {} } 在 @main 中, 使用 .environmentObject(API()) 将网络请求加入到全局环境中, 在每个页面用到它的地方使用 @EnvironmentObject var api: API 来获取.
比如进入页面 A 需要进行网络请求, 则:
struct A: View { @EnvironmentObject var api: API @State private var data: [SomeResponseType] = [] var body: View { VStack {} .task { let r = try? await api.request1() if let d = r { data = d } } } } 但这样的代码是 UIKit 的思路, 在 SwiftUI 中应该使用 Model 层进行网络请求, 应该这样添加一层 Model:
class ModelA: ObservableObject { @Published var data: [SomeResponseType] init() { // do the request and then init data using the response } } 在页面中只需要
struct A: View { @StateObject private var model = ModelA() var body: View { VStack { model.data... } } } 但现在问题出现了, 在 Model 里面无法直接使用上面封装的 API, 也就是在 model 的初始化函数中, 无法获取 environment 数据. 于是只能修改一下 API, 使其变成单例:
class API: ObservableObject { static let shared = API() ... } 这样在 model 里直接可以使用它了.
虽然这样看起来解决了, 但感觉代码结构还需要调整一下, 但不知道如何调整. 请大佬指点指点
1 elshir 2022-12-13 14:55:15 +08:00 init 中虽然不能用 api ,但是你可以用 .onAppear{} 调用 api environment.. struct A: View { @EnvironmentObject var api: API @State private var data: [SomeResponseType] = [] var body: View { VStack { } } } } |
2 elshir 2022-12-13 14:56:20 +08:00 还没打完就提交了... VStack { .... } .onAppear { api.request1() } |
3 ethusdt OP @elshir 可能你忽略了中间的信息. 发帖的目的是为了优化掉“在 .task/onAppear 中进行网络请求” 这一问题. |
4 elshir 2022-12-13 15:06:12 +08:00 @FaiChou 哦哦哦,对,抱歉,不过我自己封装的 service 会偏向于生成一个 singleton 使用,和你最后的解决方式一致 |
5 MakHoCheung 2022-12-13 15:14:08 +08:00 我觉得你一开始的方式没问题,苹果官方例子都是这样子,没必要优化掉 task modifier 摘自 Food Truck ``` struct TruckWeatherCard: View { var location: CLLocation var headerUsesNavigationLink: Bool = false var navigation: TruckCardHeaderNavigation = .navigationLink @State private var forecast: TruckWeatherForecast = placeholderForecast var body: some View { VStack { CardNavigationHeader(panel: .city(City.sanFrancisco.id), navigation: navigation) { Label("Forecast", systemImage: "cloud.sun") } chart .frame(minHeight: 180) } .padding(10) .background() .task { do { let weather = try await WeatherService.shared.weather(for: location, including: .hourly).forecast forecast = TruckWeatherForecast(entries: weather.map { .init( date: $0.date, degrees: $0.temperature.converted(to: .fahrenheit).value, isDaylight: $0.isDaylight ) }) } catch { print("Could not load weather", error.localizedDescription) } } } ................ } ``` |
6 ethusdt OP @MakHoCheung 主要是 task 和 onAppear 在 navigation back 的时候还会重复执行, 只能另外写一个类似 viewDidLoad 的 modifier 去解决. 所以想苹果自带这方法的原因可能是我的实现方式有问题 |
7 MakHoCheung 2022-12-13 15:27:48 +08:00 @FaiChou 这就是要看需求了,有的需要返回上一页时候做一下刷新,有的不需要,你也可以在 task 里面做判断是否要真正执行网络请求。有个堪称 SwiftUI 架构救星 TCA ,https://www.fatbobman.com/posts/the_Composable_Architecture/ |