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

有.NET背景,如何从Alamofire返回ViewModel格式的数据?

关于SwiftUI中使用Alamofire处理API请求并转换为ViewModel的问题

我是SwiftUI新手,正在学习使用Alamofire进行API调用。我有C#开发背景,若代码存在问题还请见谅。我编写了一个调用API的类,了解到需要用完成处理程序(completion handler)来处理异步返回的数据,但不确定如何正确提取JSON并转为ViewModel。


我的API调用类代码

class webapilib{
var apiURLBase = "https://secreturl.com/api/"
init() {
        
}
func getFriends(completionHandler:  ) {
    performRequest()
}
func   performRequest () {
    let  request =  apiURLBase
    var url =  request + "Friends/GetAllParentsFriends?parentId=1"
    AF.request(url).responseData { response in
            switch response.result {
            case .success(let value):
                return String(data: value)
            case .failure(let error):
                print(error)
            }
}

API返回的数据示例

{"parents":[{"id":1,"type":null,"firstName":"Murry","groupId":null,"children":null,"emmailAddress":null,"surname":"Goldberg","name":null,"isParent":null,"isDeleted":false,"isActive":true,"createdBy":null,"lastModifiedBy":null,"lastUpdatedDate":null,"createdDate":null}],"children":[{"id":24864,"type":null,"orginizationId":null,"personId":null,"coachId":null,"teamId":null,"playerLevel":"A","fullName":"Audrey Lind","firstName":"Audrey","surname":"Lind","year":2008,"weight":null,"gender":null,"photo":null,"orgId":null,"ageGroup":null,"age":null,"emailAddress":"tandklind@msn.com","conditioningWorkouts":null,"conditioningWorkout":null,"bookings":null,"bikeWorkOuts":null,"isDeleted":false,"notes":null,"defaultTB":150.00,"defaultOP":15.00,"defaultPU":0.00,"defaultPB":null,"defaultBP":15.00,"defaultTBReps":5.00,"defaultOPReps":0.00,"defaultPUReps":10.00,"defaultPBReps":null,"defaultBPReps":0.00,"defaultAdvancedPuReps":0.00,"defaultAdvancedPu":0.00,"defaultPuSeconds":null,"defaultBroadJumpFeet":null,"defaultBroadJumpInches":null,"defaultTwentyFiveYards":null,"defaultOneFityYards":null,"status":0,"isActive":true,"createdBy":"System Import","lastModifiedBy":"Brandon","lastUpdatedDate":"2022-03-29T15:00:28.944125","createdDate":"2021-12-17T01:07:01.1819126"},{"id":24866,"type":null,"orginizationId":null,"personId":null,"coachId":null,"teamId":null,"playerLevel":"A","fullName":"Ellarae Atkinson","firstName":"Ellarae","surname":"Atkinson","year":2009,"weight":null,"gender":null,"photo":null,"orgId":null,"ageGroup":null,"age":null,"emailAddress":"atkinson_06@comcast.net","conditioningWorkouts":null,"conditioningWorkout":null,"bookings":null,"bikeWorkOuts":null,"isDeleted":false,"notes":null,"defaultTB":55.00,"defaultOP":20.00,"defaultPU":0.00,"defaultPB":null,"defaultBP":15.00,"defaultTBReps":2399.00,"defaultOPReps":0.00,"defaultPUReps":0.00,"defaultPBReps":null,"defaultBPReps":0.00,"defaultAdvancedPuReps":0.00,"defaultAdvancedPu":0.00,"defaultPuSeconds":null,"defaultBroadJumpFeet":null,"defaultBroadJumpInches":null,"defaultTwentyFiveYards":null,"defaultOneFityYards":null,"status":0,"isActive":true,"createdBy":"System Import","lastModifiedBy":null,"lastUpdatedDate":"2022-02-09T07:09:32.994522","createdDate":"2021-12-17T01:07:09.6571143"}]}

按钮调用API的代码

func callApi()
{
  let api  = thehockeylabapi().self      
  let test: () = api.getFriends()
}
Button("getFriends", action: callApi)

解决方案步骤

1. 定义对应JSON结构的Codable模型

根据返回的JSON结构,创建遵循Codable协议的Swift模型,用于自动解析JSON:

// 家长模型
struct Parent: Codable, Identifiable {
    let id: Int
    let type: String?
    let firstName: String?
    let groupId: Int?
    let children: [Child]?
    let emmailAddress: String?
    let surname: String?
    let name: String?
    let isParent: Bool?
    let isDeleted: Bool
    let isActive: Bool
    let createdBy: String?
    let lastModifiedBy: String?
    let lastUpdatedDate: String?
    let createdDate: String?
}

// 孩子模型
struct Child: Codable, Identifiable {
    let id: Int
    let type: String?
    let orginizationId: Int?
    let personId: Int?
    let coachId: Int?
    let teamId: Int?
    let playerLevel: String?
    let fullName: String?
    let firstName: String?
    let surname: String?
    let year: Int?
    let weight: Double?
    let gender: String?
    let photo: String?
    let orgId: Int?
    let ageGroup: String?
    let age: Int?
    let emailAddress: String?
    let conditioningWorkouts: [String]?
    let conditioningWorkout: String?
    let bookings: [String]?
    let bikeWorkOuts: [String]?
    let isDeleted: Bool
    let notes: String?
    let defaultTB: Double?
    let defaultOP: Double?
    let defaultPU: Double?
    let defaultPB: Double?
    let defaultBP: Double?
    let defaultTBReps: Double?
    let defaultOPReps: Double?
    let defaultPUReps: Double?
    let defaultPBReps: Double?
    let defaultBPReps: Double?
    let defaultAdvancedPuReps: Double?
    let defaultAdvancedPu: Double?
    let defaultPuSeconds: Double?
    let defaultBroadJumpFeet: Int?
    let defaultBroadJumpInches: Int?
    let defaultTwentyFiveYards: Double?
    let defaultOneFityYards: Double?
    let status: Int
    let isActive: Bool
    let createdBy: String?
    let lastModifiedBy: String?
    let lastUpdatedDate: String?
    let createdDate: String?
}

// 顶层响应模型
struct FriendsResponse: Codable {
    let parents: [Parent]
    let children: [Child]
}

2. 修正API服务类,完善Completion Handler和JSON解析

修改API类,补全completion handler类型,并用Alamofire的responseDecodable直接解析数据:

import Alamofire

class WebAPILib {
    let apiURLBase = "https://secreturl.com/api/"
    
    func getFriends(completion: @escaping (Result<FriendsResponse, Error>) -> Void) {
        let endpoint = "Friends/GetAllParentsFriends?parentId=1"
        guard let url = URL(string: apiURLBase + endpoint) else {
            completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil)))
            return
        }
        
        AF.request(url).responseDecodable(of: FriendsResponse.self) { response in
            switch response.result {
            case .success(let data):
                completion(.success(data))
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
}

3. 在SwiftUI视图中处理异步数据并展示卡片列表

用@State管理数据状态,确保异步请求返回后更新UI:

struct FriendsView: View {
    @State private var friendsResponse: FriendsResponse?
    @State private var isLoading = false
    @State private var errorMessage: String?
    
    private let api = WebAPILib()
    
    var body: some View {
        NavigationStack {
            VStack {
                if isLoading {
                    ProgressView("加载中...")
                } else if let error = errorMessage {
                    Text("出错了:\(error)")
                        .foregroundColor(.red)
                } else if let response = friendsResponse {
                    List {
                        Section("家长") {
                            ForEach(response.parents) { parent in
                                CardView(title: parent.firstName ?? "未知", subtitle: parent.surname ?? "")
                            }
                        }
                        
                        Section("孩子") {
                            ForEach(response.children) { child in
                                CardView(title: child.fullName ?? "未知", subtitle: child.emailAddress ?? "")
                            }
                        }
                    }
                } else {
                    Text("点击按钮加载数据")
                }
            }
            .navigationTitle("我的好友")
            .toolbar {
                Button("获取好友") {
                    callApi()
                }
            }
        }
    }
    
    private func callApi() {
        isLoading = true
        errorMessage = nil
        
        api.getFriends { result in
            DispatchQueue.main.async {
                isLoading = false
                switch result {
                case .success(let response):
                    friendsResponse = response
                case .failure(let error):
                    errorMessage = error.localizedDescription
                }
            }
        }
    }
}

// 自定义卡片视图
struct CardView: View {
    let title: String
    let subtitle: String
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(title)
                .font(.headline)
            Text(subtitle)
                .font(.subheadline)
                .foregroundColor(.secondary)
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .padding(.horizontal)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 14:55:18