封装了几个网络请求:
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
FaiChou 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
FaiChou 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/
|