我有三点看法:ExerciseList视图、ExerciseView和SetsView。其中两个视图之间的转换需要很长时间(5 - 15秒),我正在尝试诊断并解决其中的原因。
ExerciseListView列出了练习,并允许用户启动会话(其返回的session_id由后面的视图使用),ExerciseView包含每个练习的SetsView。此外,我还有一个exerciseViewModel,它从API获取练习,并将会话发布到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
}
}
我的问题是,在ExerciseListView中点击"开始锻炼"按钮后,在它打开ExerciseView之前,有一个很大的滞后。它必须进行API调用,并为锻炼中的每个锻炼加载一个视图,但考虑到最多这就像7,它需要这么长时间是奇怪的。
单击"开始"按钮时,以下是响应示例:
- 开始初始化集合视图,使用会话进行练习:台式压力机**
- 开始初始化集合视图,使用会话进行练习:台式压力机**
- 开始初始化集合视图,使用会话进行练习:上拉**
- 开始初始化集合视图,使用会话进行练习:上拉**
- 开始初始化集合视图,使用会话进行练习:倾斜哑铃压力机**
- 开始初始化集合视图,使用会话进行练习:倾斜哑铃压力机**
- 开始初始化集合视图,使用会话进行练习:哑铃排**
- 开始初始化集合视图,使用会话进行练习:哑铃排**
- 2023 - 01 - 20 00:54:09.174902 - 0600身体跟踪器[4930:2724343][连接] nw_connection_add_timestamp_locked_on_nw_queue [C1]命中最大时间戳计数,将开始丢弃事件**
- 开始初始化集合视图,使用会话进行练习:-1个套管**
- 开始初始化集合视图,使用会话进行练习:-1个套管**
- 开始初始化集合视图,使用会话进行练习:下降哑铃按压**
- 开始初始化集合视图,使用会话进行练习:下降哑铃按压**
- 发送训练ID为3的训练**
- 开始初始化集合视图,使用会话进行练习:102卧推**
- 开始初始化集合视图,使用会话进行练习:102拉起**
- 开始初始化集合视图,使用会话进行练习:102倾斜哑铃压力机**
- 开始初始化集合视图,使用会话进行练习:哑铃街102号**
- 开始初始化集合视图,使用会话进行练习:102耸肩**
- 开始初始化集合视图,使用会话进行练习:102下降哑铃按压器**
- 开始初始化集合视图,使用会话进行练习:102卧推**
- 开始初始化集合视图,使用会话进行练习:102拉起**
- 开始初始化集合视图,使用会话进行练习:102倾斜哑铃压力机**
- 开始初始化集合视图,使用会话进行练习:哑铃街102号**
- 开始初始化集合视图,使用会话进行练习:102耸肩**
- 开始初始化集合视图,使用会话进行练习:102下降哑铃按压器**
为什么init语句要重复这么多次呢?6个练习的初始化次数不是6次,而是24次。我假设前12个init的值是-1,因为这是我在exerciseViewModel中示例化session_id的值,但是有没有更好的方法让它工作呢?我试过使用DispatchSemaphore,但是应用程序会因为某些原因而卡住。我应该传递整个视图模型而不是仅仅传递id吗?或者ag是由我遗漏的其他东西创建的。我相当确信这不是API。因为其他的电话都不需要花很长时间。请帮助我正确地设置这个。
1条答案
按热度按时间xqkwcwgp1#
ExerciseViewModel
应该是视图层次结构顶部的状态对象,之后是可观察的对象,因此在
ExerciseView
中应为您希望在任何地方都使用相同的示例。
ExerciseListView.workout
似乎是一个有点奇怪的属性,不清楚它是从哪里来的,但如果它是从外部设置的不可变属性,请将其设置为let
。不要太在意SwiftUI视图初始化的次数。只要框架观察到published、state或其他特殊属性发生变化,它就会重建视图层次结构。您的视图本质上应该可以自由创建,因为您不应该在init方法中执行任何繁重的操作。
这让我怀疑这句台词:
可能是你的问题的一部分,你没有展示它的实现,但是如果你在初始化时创建它,那么它应该是一个状态对象,你可以这样创建:
这样可以确保视图模型只创建一次。
最后一件看起来有点可疑的事情是
apiManager.getToken()
-这看起来很可能涉及API调用,但您没有将其视为异步的。为了真正弄清楚发生了什么,你已经从日志记录开始了一个良好的开端,但是你还可以添加更多的日志记录,你还可以设置一些断点并单步执行代码,也许在程序挂起时暂停调试器中的程序,并检查仪器中的CPU使用率或配置文件。