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

SwiftUI视图加载前从服务器获取数据并显示在按钮文本的实现及错误解决

解决SwiftUI异步数据加载的闭包捕获错误与数据展示问题

首先,你碰到的Escaping closure captures mutating 'self' parameter错误,核心原因是SwiftUI的View是值类型,在init方法里self处于可变(mutating)状态,而逃逸闭包不能捕获这种可变的self。另外你的代码还有几个细节问题需要修正,比如网络请求的completion没有正确调用、使用NS类型不够Swift化等。下面是完整的解决方案:

1. 重构GetLocations类

先把Objective-C风格的NSArray/NSDictionary换成Swift原生类型,同时确保异步请求完成后正确调用completion返回数据:

import Foundation

class GetLocations: ObservableObject {
    // 用Swift原生数组替代NSArray,类型更安全
    @Published var locations: [[String: Any]] = []
    
    func fetchLocationNames(action: String, id: String, completion: @escaping ([[String: Any]]) -> Void) {
        // 先构造你的请求(这里需要补充实际的服务器接口地址和请求参数)
        guard let url = URL(string: "你的服务器接口地址") else {
            print("无效的URL")
            completion([])
            return
        }
        var request = URLRequest(url: url)
        // 补充请求方法、参数等(示例为POST请求)
        request.httpMethod = "POST"
        let params = ["Action": action, "Id": id]
        request.httpBody = try? JSONSerialization.data(withJSONObject: params)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let session = URLSession.shared
        session.dataTask(with: request) { [weak self] data, response, error in
            if let error = error {
                print("请求错误: \(error)")
                completion([])
                return
            }
            
            guard let data = data else {
                print("未获取到数据")
                completion([])
                return
            }
            
            do {
                if let parseResult = try JSONSerialization.jsonObject(with: data) as? [String: Any],
                   let infoList = parseResult["InfoList"] as? [[String: Any]] {
                    // 回到主线程更新UI相关数据(必须在主线程操作)
                    DispatchQueue.main.async {
                        self?.locations = infoList
                    }
                    completion(infoList)
                } else {
                    print("数据格式无效")
                    completion([])
                }
            } catch {
                print("JSON解析错误: \(error)")
                completion([])
            }
        }.resume()
    }
}

2. 修改SwiftUI视图实现

用@ObservedObject监听数据变化替代@State,并在onAppear中触发数据请求(而非init),同时安全获取数组第一个元素的Name值:

import SwiftUI

struct GetLocationData: View {
    // 用ObservedObject监听GetLocations的Published属性变化,自动刷新视图
    @ObservedObject private var locationFetcher = GetLocations()
    
    var body: some View {
        VStack(spacing: 20) {
            Button(action: {}) {
                // 安全获取第一个元素的Name,无数据时显示默认文本
                Text(locationFetcher.locations.first?["Name"] as? String ?? "Select Menu")
                    .frame(minWidth: 0, maxWidth: 500)
                    .padding()
                    .background(Color.clear)
                    .foregroundColor(.black)
                    .font(.custom("Open Sans", size: 18))
                    .overlay(
                        RoundedRectangle(cornerRadius: 10)
                            .stroke(Color.gray, lineWidth: 2)
                    )
            }
        }
        // 视图出现时触发数据请求,此时self已初始化完成,避免闭包捕获问题
        .onAppear {
            locationFetcher.fetchLocationNames(
                action: "US",
                id: "ba6fcd92-3d1e-4fb6-b135-0c47ea1815cd"
            ) { locations in
                // 可选:在这里处理completion返回的额外逻辑
                print("成功获取\(locations.count)条位置数据")
            }
        }
    }
}

关键修改点说明

  • 闭包捕获错误解决:用onAppear替代init触发异步请求,此时self已经初始化完成,不再是mutating状态,彻底避免了逃逸闭包捕获mutating self的问题。
  • 类型安全优化:把NSArray换成Swift原生的[[String: Any]],减少强制类型转换的风险,代码更符合Swift的类型安全特性。
  • UI自动刷新:通过@ObservedObject和@Published的组合,当locations数组更新时,视图会自动刷新,按钮文本也会同步更新。
  • 主线程更新:网络请求的回调在后台线程,更新@Published属性时切换到主线程,确保UI操作的安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 06:18:12