You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftData健身应用问题:Workout对象提前实例化且无法销毁

基于SwiftData的健身应用实例化与对象销毁问题解决

问题背景

我开发了一款基于SwiftData的健身应用,用户点击“Start Workout”按钮后会跳转到CurrentWorkoutView,在该视图中实例化Workout对象,用户后续可选择保存或取消训练。目前遇到两个问题:

  • 应用刚启动时,Workout对象就已被实例化;
  • 用户保存或取消训练后,Workout对象无法被销毁。

简化代码

HomeView.swift

...

NavigationLink("Start Workout") {
    CurrentWorkoutView()
}

...

CurrentWorkoutView.swift

@Environment(\.dismiss) var dismiss
@Environment(\.modelContext) var context
    
@Bindable var workout: Workout = Workout()

var body: some View {

    ...

    .toolbar {
        ToolbarItem(placement: .confirmationAction) {
            Button("Save") {
                workout.end = Date.now
                context.insert(workout)
                dismiss()
            }
        }
            
        ToolbarItem(placement: .cancellationAction) {
            Button("Cancel") {
                context.delete(workout)
                try! context.save()
                dismiss()
            }
        }
    }
}

我曾尝试将workout设为可空类型,但由于被@Bindable修饰无法实现,而该对象必须为Bindable才能支持标题、描述等内容的编辑。


解决方案

1. 延迟Workout对象实例化

不要在CurrentWorkoutView的属性定义处直接初始化Workout,通过初始化方法或懒加载确保仅当视图被加载(用户点击跳转后)才创建对象:

修改CurrentWorkoutView:

@Environment(\.dismiss) var dismiss
@Environment(\.modelContext) var context

@Bindable var workout: Workout

// 通过初始化方法创建实例,仅在视图初始化时执行
init() {
    self.workout = Workout()
}

这样应用启动时不会提前创建Workout,只有用户进入CurrentWorkoutView时才会实例化对象。

2. 正确处理取消时的对象销毁

当前取消按钮调用context.delete(workout)无效,因为未保存的Workout还未被加入modelContext。只需放弃对象引用,ARC会自动回收:

修改取消按钮逻辑:

ToolbarItem(placement: .cancellationAction) {
    Button("Cancel") {
        // 仅当对象已被上下文管理时才执行删除
        if workout.isPersistent {
            context.delete(workout)
            try? context.save()
        }
        dismiss()
    }
}

同时优化保存按钮的错误处理(替换强制try!):

Button("Save") {
    workout.end = Date.now
    context.insert(workout)
    do {
        try context.save()
    } catch {
        print("保存训练失败: \(error.localizedDescription)")
    }
    dismiss()
}

3. 可选类型配合@Bindable的实现方案

如果需要用可选类型,可通过@State包裹可选对象,再通过计算属性提供@Bindable实例:

@Environment(\.dismiss) var dismiss
@Environment(\.modelContext) var context

@State private var workout: Workout? = nil
@Bindable var bindableWorkout: Workout {
    guard let existingWorkout = workout else {
        let newWorkout = Workout()
        self.workout = newWorkout
        return newWorkout
    }
    return existingWorkout
}

var body: some View {
    VStack {
        // 用bindableWorkout绑定编辑控件
        TextField("训练标题", text: $bindableWorkout.title)
        // ...其他编辑内容
    }
    .onAppear {
        // 视图出现时才实例化对象
        if workout == nil {
            workout = Workout()
        }
    }
    .toolbar {
        ToolbarItem(placement: .confirmationAction) {
            Button("Save") {
                guard let workout = workout else { return }
                workout.end = Date.now
                context.insert(workout)
                do {
                    try context.save()
                } catch {
                    print("保存失败: \(error)")
                }
                dismiss()
            }
        }
        ToolbarItem(placement: .cancellationAction) {
            Button("Cancel") {
                guard let workout = workout else { 
                    dismiss()
                    return 
                }
                if workout.isPersistent {
                    context.delete(workout)
                    try? context.save()
                }
                // 置空引用触发ARC回收
                self.workout = nil
                dismiss()
            }
        }
    }
}

这种方式既实现了延迟实例化,又能在取消时通过置空引用销毁对象,同时满足绑定编辑的需求。


内容的提问来源于stack exchange,提问作者Thiago Dias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:50:56