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

3pvhb19x  于 2023-01-22  发布在  iOS
关注(0)|答案(1)|浏览(85)

我有三点看法: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。因为其他的电话都不需要花很长时间。请帮助我正确地设置这个。

xqkwcwgp

xqkwcwgp1#

ExerciseViewModel应该是视图层次结构顶部的状态对象,之后是可观察的对象,因此

@StateObject var exerciseViewModel =  ExerciseViewModel()

ExerciseView中应为

@ObservedObject var exerciseViewModel: ExerciseViewModel

您希望在任何地方都使用相同的示例。
ExerciseListView.workout似乎是一个有点奇怪的属性,不清楚它是从哪里来的,但如果它是从外部设置的不可变属性,请将其设置为let

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

这让我怀疑这句台词:

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

可能是你的问题的一部分,你没有展示它的实现,但是如果你在初始化时创建它,那么它应该是一个状态对象,你可以这样创建:

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

这样可以确保视图模型只创建一次。
最后一件看起来有点可疑的事情是apiManager.getToken()-这看起来很可能涉及API调用,但您没有将其视为异步的。
为了真正弄清楚发生了什么,你已经从日志记录开始了一个良好的开端,但是你还可以添加更多的日志记录,你还可以设置一些断点并单步执行代码,也许在程序挂起时暂停调试器中的程序,并检查仪器中的CPU使用率或配置文件。

相关问题