V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
FaiChou
V2EX  ›  Swift

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

  •  
  •   FaiChou ·
    FaiChou · 2022-12-13 14:38:47 +08:00 · 1293 次点击
    这是一个创建于 743 天前的主题,其中的信息可能已经有所发展或是发生改变。

    封装了几个网络请求:

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

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

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

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


    }
    ```
    FaiChou
        6
    FaiChou  
    OP
       2022-12-13 15:19:14 +08:00
    @MakHoCheung 主要是 task 和 onAppear 在 navigation back 的时候还会重复执行, 只能另外写一个类似 viewDidLoad 的 modifier 去解决. 所以想苹果自带这方法的原因可能是我的实现方式有问题 😄
    MakHoCheung
        7
    MakHoCheung  
       2022-12-13 15:27:48 +08:00
    @FaiChou 这就是要看需求了,有的需要返回上一页时候做一下刷新,有的不需要,你也可以在 task 里面做判断是否要真正执行网络请求。有个堪称 SwiftUI 架构救星 TCA ,https://www.fatbobman.com/posts/the_Composable_Architecture/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1781 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:31 · PVG 00:31 · LAX 08:31 · JFK 11:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.