繁体   English   中英

为什么实例化的视图比预期的多?

[英]Why are more views being instantiated than expected?

我有三个视图:ExerciseList View、ExerciseView 和 SetsView。 其中两个之间的转换需要很长时间(5-15 秒),我正在尝试诊断并解决原因。

ExerciseListView 列出练习并让用户启动一个 session(其返回的 session_id 被后面的 View 使用),并且 ExerciseView 包含每个练习的 SetsView。 此外,我还有一个 exerciseViewModel,它从 API 获取练习并将 session 发送到 API。这是代码:

struct ExerciseListView: View {
    @StateObject var exerciseViewModel = ExerciseViewModel()
    var workout: Workout
    
    var body: some View {
        NavigationStack {
            ScrollView {
                VStack{
                    ForEach(exerciseViewModel.exercises, id: \.exercise_id) { exercise in
                        exerciseListRow(exercise: exercise)
                    }
                }.navigationTitle(workout.workout_name)
                    .toolbar{startButton}
            }
        }.onAppear {
            self.exerciseViewModel.fetch(workout_id: workout.workout_id)
        }
    }
    
    var startButton: some View {
        NavigationLink(destination: ExerciseView(exerciseViewModel: exerciseViewModel, workout: workout)) {
            
            Text("Start Workout")
        }
    }
}

struct exerciseListRow: View {
    let exercise: Exercise
    
    var body: some View {
        Text(String(exercise.set_number) + " sets of " + exercise.exercise_name + "s").padding(.all)
            .font(.title2)
            .fontWeight(.semibold)
            .frame(width: 375)
            .foregroundColor(Color.white)
            .background(Color.blue)
            .cornerRadius(10.0)
    }
}
struct Exercise: Hashable, Codable {
    var exercise_id: Int
    var exercise_name: String
    var set_number: Int
}

class ExerciseViewModel: ObservableObject {
    var apiManager = ApiManager()
    @Published var exercises: [Exercise] = []
    @Published var session_id: Int = -1
    
    func fetch(workout_id: Int) {
        self.apiManager.getToken()
        
        print("Calling workout data with workout_id " + String(workout_id))
        guard let url = URL(string: (self.apiManager.myUrl + "/ExercisesInWorkout"))
        else {
            print("Error: Something wrong with url.")
            return
        }
        
        var urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
        urlRequest.allHTTPHeaderFields = [
            "Token": self.apiManager.token
        ]
        urlRequest.httpMethod = "POST"
        
        let body = "workout_id="+String(workout_id)
        urlRequest.httpBody = body.data(using: String.Encoding.utf8)
        
        let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
            guard let data = data, error == nil else {
                return
            }
            //Convert to json
            do {
                let exercises = try JSONDecoder().decode([Exercise].self, from: data)
                DispatchQueue.main.async {
//                    print(exercises)
                    self?.exercises = exercises
                }
            }
            catch {
                print("Error: something went wrong calling api", error)
            }
        }
        task.resume()
    }
    
    func sendSession(workout_id: Int) {
        self.apiManager.getToken()
        
        print("Sending session with workout_id " + String(workout_id))
        guard let url = URL(string: (self.apiManager.myUrl + "/Session"))
        else {
            print("Error: Something wrong with url.")
            return
        }
        
        var urlRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
        urlRequest.allHTTPHeaderFields = [
            "Token": self.apiManager.token
        ]
        urlRequest.httpMethod = "POST"
        
        let body = "workout_id="+String(workout_id)
        urlRequest.httpBody = body.data(using: String.Encoding.utf8)
        
        let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] data, _, error in
            guard let data = data, error == nil else {
                return
            }
            
            do {
                let decoded = try JSONDecoder().decode(Int.self, from: data)
                DispatchQueue.main.async {
                    self?.session_id = decoded
                }
            }
            catch {
                print("Error: something went wrong calling api", error)
            }
        }
        task.resume()
    }
}
struct ExerciseView: View {
    @StateObject var exerciseViewModel =  ExerciseViewModel()
    var workout: Workout
    @State var session_started: Bool = false
    
    var body: some View {
        NavigationStack {
            VStack {
                List {
                    Section(header: Text("Enter exercise data")) {
                        ForEach(exerciseViewModel.exercises, id: \.exercise_id) { exercise in
                            
                            NavigationLink(destination: SetsView(workout: workout, exercise: exercise, session_id: exerciseViewModel.session_id)) {
                                Text(exercise.exercise_name)
                            }
                        }
                    }
                }.listStyle(GroupedListStyle())
            }.navigationTitle(workout.workout_name)
        }.onAppear {
            if !self.session_started {
                self.exerciseViewModel.sendSession(workout_id: workout.workout_id)
                self.session_started = true
            }
        }
    }
}
struct SetsView: View {
    
    var workout: Workout
    var exercise: Exercise
    var session_id: Int
    @ObservedObject var setsViewModel: SetsViewModel
    @State var buttonText: String = "Submit All"
    @State var lastSetsShowing: Bool = false
       
    init(workout: Workout, exercise: Exercise, session_id: Int) {
        print("Starting init for sets view with exercise with session:", session_id, exercise.exercise_name)
        self.workout = workout
        self.exercise = exercise
        self.session_id = session_id
        
        self.setsViewModel = SetsViewModel(session_id: session_id, workout_id: workout.workout_id, exercise: exercise)
    }
        
    var body: some View {
        ScrollView {
            VStack {
                ForEach(0..<exercise.set_number) {set_index in
                    SetView(set_num: set_index, setsViewModel: setsViewModel)
                    Spacer()
                }
                submitButton
            }.navigationTitle(exercise.exercise_name)
                .toolbar{lastSetsButton}
        }.sheet(isPresented: $lastSetsShowing) { LastSetsView(exercise: self.exercise, workout_id: self.workout.workout_id)
            
        }
    }
    
    var lastSetsButton: some View {
        Button("Show Last Sets") {
            self.lastSetsShowing = true
        }
    }
    
    var submitButton: some View {
        
        Button(self.buttonText) {
            if entrysNotNull() {
                self.setsViewModel.postSets()
                self.buttonText = "Submitted"
            }
        }.padding(.all).foregroundColor(Color.white).background(Color.blue).cornerRadius(10)
    }
    
    func entrysNotNull() -> Bool {
        if (self.buttonText != "Submit All") {return false}
        for s in self.setsViewModel.session.sets {
            if ((s.weight == nil || s.reps == nil) || (s.quality == nil || s.rep_speed == nil)) || (s.rest_before == nil) {
                return false}
        }
        return true
    }
}

我的问题是在打开 ExerciseView 之前点击 ExerciseListView 中的“开始锻炼”按钮后有很大的滞后。 它必须进行 API 调用并为锻炼中的每个练习加载视图,但考虑到最多这就像 7,奇怪的是需要这么长时间。

单击“开始”按钮时,这里是一个响应示例:

使用 session 开始 sets view 的初始化:-1 卧推

使用 session 开始 sets view 的初始化:-1 卧推

使用 session 开始 set view 的初始化:-1 Pull Up

使用 session 开始 set view 的初始化:-1 Pull Up

使用 session 开始设置视图的初始化:-1 上斜哑铃推举

使用 session 开始设置视图的初始化:-1 上斜哑铃推举

使用 session 开始 sets view 的初始化:-1 Dumbell Row

使用 session 开始 sets view 的初始化:-1 Dumbell Row

2023-01-20 00:54:09.174902-0600 BodyTracker[4930:2724343] [连接] nw_connection_add_timestamp_locked_on_nw_queue [C1] 达到最大时间戳计数,将开始丢弃事件

使用 session 开始 sets view 的初始化:-1 耸肩

使用 session 开始 sets view 的初始化:-1 耸肩

使用 session 开始锻炼组视图的初始化:-1 下斜哑铃推举

使用 session 开始锻炼组视图的初始化:-1 下斜哑铃推举

发送带有 workout_id 3 的 session

session:102 卧推

使用 session 开始 sets view 的初始化:102 Pull Up

session:102 上斜哑铃卧推

使用 session 开始锻炼集合视图:102 哑铃划船

使用 session 开始 set view 的初始化:102 次耸肩

使用 session 开始锻炼组视图的初始化:102 下斜哑铃推举

session:102 卧推

使用 session 开始 sets view 的初始化:102 Pull Up

session:102 上斜哑铃卧推

使用 session 开始锻炼集合视图:102 哑铃划船

使用 session 开始 set view 的初始化:102 次耸肩

使用 session 开始锻炼组视图的初始化:102 下斜哑铃推举

为什么 init 语句会重复这么多次? 6 个练习不是 6 个初始化,而是 24 个。我假设前 12 个初始化是 -1,因为这是我在 exerciseViewModel 中实例化 session_id 的内容,但是有没有更好的方法让它工作? 我试过使用 DispatchSemaphore,但该应用程序由于某种原因被卡住了。 我应该传递整个视图模型而不仅仅是 id 吗? 或者也许 ag 是由我缺少的其他东西创建的。 我相当有信心它不是 API,因为其他电话都不会花费很长时间。 请帮助我以正确的方式进行设置。

ExerciseViewModel应该是视图层次结构顶部的 state object,之后是可观察的 object,所以

@StateObject var exerciseViewModel =  ExerciseViewModel()

ExerciseView中应该是

@ObservedObject var exerciseViewModel: ExerciseViewModel

您希望在所有地方都使用同一个实例。

ExerciseListView.workout似乎是一个奇怪的属性,不清楚它从何而来,但如果它是从外部设置的不可变的东西,请将其设置为let

不要太在意 SwiftUI 视图的初始化次数 只要框架观察到已发布或 state 或其他特殊属性发生变化,就会重建视图层次结构。 您的视图基本上应该可以自由创建,因为您不应该在 init 方法中做任何繁重的事情。

这让我怀疑这一行:

self.setsViewModel = SetsViewModel(session_id: session_id, workout_id: workout.workout_id, exercise: exercise)

可能是你问题的一部分。 你没有显示它的实现,但如果你在初始化时创建它,那么它应该是 state object,你可以这样创建:

@StateObject var setsViewModel: SetsViewModel
...
//in your init
_setsViewModel = StateObject(wrappedValue: SetsViewModel(session_id: session_id, workout_id: workout.workout_id, exercise: exercise))

这确保视图 model 只会被创建一次。

看起来有点可疑的最后一件事是apiManager.getToken() - 这看起来很可能涉及 API 调用,但您没有将其视为异步。

要真正弄清楚发生了什么,您已经开始使用日志记录,但您可以添加更多内容。 您还可以放置一些断点并逐步执行代码,也许在程序挂起时在调试器中暂停程序,并检查 CPU 使用率或仪器中的配置文件。

问题未解决?试试以下方法:

为什么实例化的视图比预期的多?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2023 STACKOOM.COM