我正在 Xcode 上开发一个 Mac OS 应用程序,该应用程序从 API 中提取足球排名并将其显示在视图中。我注意到我的内存不断增加,并且在使用 Xcode Memory Graph 之后,我相信我的问题是 ViewModel 没有被 deinit,因此仍然被分配内存。例如下图中,从
BundesligaView
来回3次就创建了3个独立的模型。
下面的代码比我原来的应用程序简化了很多。当从 LeagueIndex -> BundesligaView 移动时,会创建一个 viewModel。我不确定是否应该在某个地方添加一个[弱自我]?
// VIEWMODEL
import Foundation
class BundesligaViewModel: ObservableObject {
@Published var standings: [BundesligaModel] = []
@Published var loadingState: LoadingState = .idle
@MainActor
func fetchData(_ league: String? = nil) async {
loadingState = .loading
do {
standings = try await NetworkManager.shared.fetchBundesligaStandings(league!)
loadingState = .loaded
} catch {
print("Error fetching standings: \(error)")
loadingState = .error
}
}
init() {
print("VIEWMODEL CREATED")
}
deinit {
print("VIEWMODEL DEINT")
}
}
enum LoadingState {
case idle
case loading
case loaded
case error
}
// MODEL
import Foundation
struct BundesligaModel: Codable {
let teamName: String
let shortName: String
let points: Int
let matches: Int
let won: Int
let draw: Int
let lost: Int
let goals: Int
let opponentGoals: Int
let goalDiff: Int
}
// VIEWS
struct LeagueIndex: View {
var body: some View {
NavigationStack {
NavigationLink(destination: BundesligaTable(league: "Bundesliga")) {
Text("Bundesliga")
}
.buttonStyle(PlainButtonStyle())
}
.navigationBarBackButtonHidden(true)
}
}
struct Footer: View {
var body: some View {
NavigationLink(destination: LeagueIndex()) {
Text("League Index").foregroundStyle(Color.red)
}
.buttonStyle(PlainButtonStyle())
}
}
struct BundesligaTable: View {
let league: String
@StateObject private var viewModel = BundesligaViewModel()
let columns = [
GridItem(.fixed(25)),
GridItem(.flexible()),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25)),
GridItem(.fixed(25))
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(Array(viewModel.standings.enumerated()), id: \.element.teamName) { index, team in
Text("\(index + 1)")
Text(team.shortName)
Text("\(team.matches)")
Text("\(team.won)")
Text("\(team.draw)")
Text("\(team.lost)")
Text("\(team.goals)")
Text("\(team.opponentGoals)")
Text("\(team.goalDiff)")
Text("\(team.points)")
}
}
Footer()
}
.padding(.horizontal)
.task {
await viewModel.fetchData(league)
}
}
}
// NETWORK MANAGER
class NetworkManager {
static let shared = NetworkManager()
func fetchData<T: Decodable>(from endpoint: String) async throws -> T {
guard let url = URL(string: endpoint) else {
throw URLError(.badURL)
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(T.self, from: data)
return decodedData
} catch {
throw error
}
}
}
extension NetworkManager {
func fetchBundesligaStandings(_ league: String) async throws -> [BundesligaModel] {
return try await fetchData(from: "https://api.openligadb.de/getbltable/bl1/2023")
}
// Other leagues here
}
发现问题了!我有一个自定义页脚,在
LeagueIndex
时导航到 BundesligaView
。它本应充当 NavigationStack 中的后退按钮,但它的实现错误,导致 View 和 ViewModel 被保留。
旧页脚:
struct Footer: View {
var body: some View {
HStack {
NavigationLink(destination: LeagueIndex()) {
Text("League Index").foregroundStyle(Color.red)
}
}
}
}
新页脚:
struct Footer: View {
@Environment(\.dismiss) var dismiss
var body: some View {
HStack {
Button(action: {
dismiss()
}) {
HStack {
Text("League Index").foregroundStyle(Color.red)
}
}
}
}
}