簡體   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-2024 STACKOOM.COM