![](/img/trans.png)
[英]Recording with AVAudioSession - Why do I get more bytes than expected?
[英]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.