SwiftUI 中如何优化下面这种场景?

2022-12-13 14:38:47 +08:00
 FaiChou

封装了几个网络请求:

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 里直接可以使用它了.

虽然这样看起来解决了, 但感觉代码结构还需要调整一下, 但不知道如何调整. 请大佬指点指点

1093 次点击
所在节点    Swift
7 条回复
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 {
}
}
}
}
elshir
2022-12-13 14:56:20 +08:00
还没打完就提交了...
VStack {
....
}
.onAppear {
api.request1()
}
FaiChou
2022-12-13 15:02:30 +08:00
@elshir 可能你忽略了中间的信息. 发帖的目的是为了优化掉“在 .task/onAppear 中进行网络请求” 这一问题.
elshir
2022-12-13 15:06:12 +08:00
@FaiChou 哦哦哦,对,抱歉,不过我自己封装的 service 会偏向于生成一个 singleton 使用,和你最后的解决方式一致
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)
}
}
}

................


}
```
FaiChou
2022-12-13 15:19:14 +08:00
@MakHoCheung 主要是 task 和 onAppear 在 navigation back 的时候还会重复执行, 只能另外写一个类似 viewDidLoad 的 modifier 去解决. 所以想苹果自带这方法的原因可能是我的实现方式有问题 😄
MakHoCheung
2022-12-13 15:27:48 +08:00
@FaiChou 这就是要看需求了,有的需要返回上一页时候做一下刷新,有的不需要,你也可以在 task 里面做判断是否要真正执行网络请求。有个堪称 SwiftUI 架构救星 TCA ,https://www.fatbobman.com/posts/the_Composable_Architecture/

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/902196

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX