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

Swift iOS项目MVVM实现代码正确性验证请求

Is Your Swift MVVM Implementation Following Best Practices?

Great job getting a working MVVM setup for your iOS project! Let’s walk through how your current code aligns with MVVM principles and break down actionable improvements to make it more idiomatic and maintainable.

What You’re Doing Right

First, let’s highlight the positives—you’ve already nailed the core separation of concerns that MVVM is built on:

  • Isolated network logic: Your service struct keeps API call code out of the view controller, which is exactly where it should be.
  • Proper data modeling: You’ve defined StudentData and Studentdetail to decode JSON responses, which is the correct way to represent raw API data.
  • Basic ViewModel layer: You created a ViewModel to transform model data for the view, which hits the core purpose of MVVM.

Areas to Refine for Better MVVM Compliance

While your code works, there are several adjustments to make it fit MVVM’s intended workflow and Swift best practices:

1. View Controller Shouldn’t Manage Data Fetching

Right now, your ViewController is directly instantiating the service, handling the completion block, and creating the ViewModel. In MVVM, the View (ViewController) should only observe changes from the ViewModel—it shouldn’t handle data fetching or ViewModel initialization directly.

2. Service Layer Needs Polish

  • Naming: Swift conventions require type names to start with a capital letter (rename service to StudentService).
  • Return complete models: Instead of splitting studentname and class into separate arrays, return the full [Studentdetail] array. This keeps data intact and avoids unnecessary manipulation in the service.
  • Error handling: Your current service doesn’t properly pass errors to the caller—use a Result type to handle success/failure explicitly.

3. ViewModel Should Own Data Logic

Your current ViewModel is just a thin wrapper around Model with renamed properties. In MVVM, the ViewModel should:

  • Handle data fetching (via the service)
  • Expose observable properties so the View can react to changes automatically
  • Contain any business logic (e.g., formatting data for display)

4. Redundant Model Layer

Your Model class is unnecessary—you already have Studentdetail which represents the raw student data. You can use Studentdetail directly in your ViewModel instead of creating a duplicate model.

5. Swift Naming Conventions

Follow standard Swift rules:

  • Use camelCase for properties (e.g., NameLabel → nameLabel, CustName → customerName)
  • Type names use PascalCase (e.g., service → StudentService)

Improved Code Example

Here’s how to refactor your code to align with MVVM best practices:

StudentService (Refactored)

import Foundation

struct StudentService {
    static let shared = StudentService()
    
    func fetchStudents(completion: @escaping (Result<[Studentdetail], Error>) -> Void) {
        guard let url = URL(string: DefaultData.Base.url) else {
            completion(.failure(NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid API URL"])))
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "NoData", code: -2, userInfo: [NSLocalizedDescriptionKey: "No data received from API"])))
                return
            }
            
            do {
                let studentData = try JSONDecoder().decode(StudentData.self, from: data)
                completion(.success(studentData.studentdetails))
            } catch {
                completion(.failure(error))
            }
        }.resume()
    }
}

StudentViewModel (With Combine for Observability)

import Foundation
import Combine

class StudentViewModel: ObservableObject {
    // Observable properties the View will bind to
    @Published var studentName: String?
    @Published var studentClass: String?
    
    private var cancellables = Set<AnyCancellable>()
    private let service: StudentService
    
    // Inject service for testability (optional but recommended)
    init(service: StudentService = .shared) {
        self.service = service
    }
    
    func loadFirstStudent() {
        service.fetchStudents()
            .receive(on: DispatchQueue.main) // Ensure UI updates happen on main thread
            .sink(receiveCompletion: { [weak self] completion in
                if case .failure(let error) = completion {
                    print("Failed to load students: \(error.localizedDescription)")
                }
            }, receiveValue: { [weak self] students in
                guard let firstStudent = students.first else { return }
                self?.studentName = firstStudent.studentname
                self?.studentClass = firstStudent.studentdetailClass
            })
            .store(in: &cancellables)
    }
}

ViewController (Simplified, Only Handles Binding)

import UIKit
import Combine

class ViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var classLabel: UILabel!
    
    private let viewModel = StudentViewModel()
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
        viewModel.loadFirstStudent()
    }
    
    private func setupBindings() {
        // Bind ViewModel properties to UI elements automatically
        viewModel.$studentName
            .assign(to: \.text, on: nameLabel)
            .store(in: &cancellables)
        
        viewModel.$studentClass
            .assign(to: \.text, on: classLabel)
            .store(in: &cancellables)
    }
}

Simplified Model & DefaultData

// Model.swift
import Foundation

struct StudentData: Codable {
    let studentdetails: [Studentdetail]
}

struct Studentdetail: Codable {
    let school, studentname, studentdetailClass, board: String
    enum CodingKeys: String, CodingKey {
        case school, studentname
        case studentdetailClass = "class"
        case board
    }
}

// DefaultData.swift
import Foundation

enum DefaultData {
    enum Base {
        static let url = "https://next.json-generator.com/api/json/get/4kobjnVE8"
    }
}

Key Takeaways from the Refactor

  • Clean separation of concerns: The View only handles UI updates, ViewModel manages data and logic, Service handles network calls, Model defines data structures.
  • Automatic UI updates: Using Combine’s @Published lets the View react to ViewModel changes without manual completion block handling.
  • Testability: Injecting the service into the ViewModel makes it easy to mock network calls for unit testing.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:24:19