
[英]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.