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

Swift MVVM架构中'self.viewModel未在super.init前初始化'错误修复

iOS MVVM+POP架构下ViewController初始化错误修复

问题描述

开发一款在CollectionView中展示公司列表的iOS应用,通过API获取数据,采用MVVM架构与面向协议编程(POP)模式。运行应用时出现错误:

Property 'self.viewModel' not initialized at super.init call

相关代码

API协议代码

protocol APIProtocol {
    func fetchData(completion : @escaping(Result<[Company],APIError>)->Void)
}

API Manager代码

enum APIError : Error {
    case URLError
    case ConnectionError
}

class APIManager: APIProtocol {
    func fetchData(completion : @escaping(Result<[Company],APIError>)->Void) {
        guard let url = URL(string: "https://fakerapi.it/api/v1/companies?_quantity=20") else {return}
        URLSession.shared.dataTask(with: url) { (data, _, error) in
            guard let data = data else {
                completion(.failure(.URLError))
                return
            }
            DispatchQueue.main.async {
                let jsonData = try? JSONDecoder().decode(InitialData.self, from: data)
                guard let jsonData = jsonData else {return}
                print(jsonData.data)
                completion(.success(jsonData.data))
            }
        }.resume()
    }
}

ViewModel代码

class CompanyViewModel {
    private let apiProtocol : APIProtocol
    weak var output : CompanyViewModelOutput?
    
    init(apiProtocol: APIProtocol) {
        self.apiProtocol = apiProtocol
    }
    
    func fetchData() {
        apiProtocol.fetchData { [weak self] result in
            switch result {
            case .success(let companyList):
                self?.output?.fetchList(companyList: companyList)
                print(companyList)
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
    }
}

ViewModel输出协议代码

protocol CompanyViewModelOutput : AnyObject {
    func fetchList(companyList : [Company])
}

ViewController代码

class ViewController: UIViewController, CompanyViewModelOutput {
    func fetchList(companyList: [Company]) {
        self.compList = companyList
    }
    
    var compList = [Company]() {
        didSet {
            self.collectionView.reloadData()
        }
    }

    @IBOutlet weak var collectionView: UICollectionView!
    private let viewModel : CompanyViewModel
    
    init(viewModel: CompanyViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
        self.viewModel.output = self
    }
    
    required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder) 
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        collectionView.dataSource = self
        viewModel.fetchData()
    }
}

extension ViewController : UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return compList.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CompanyCell
        cell.nameLabel.text = compList[indexPath.row].name
        cell.countryLabel.text = compList[indexPath.row].country
        return cell
    }
}

SceneDelegate代码

guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
let apiProtocol : APIProtocol = APIManager()
let viewModel = CompanyViewModel(apiProtocol: apiProtocol)
window?.rootViewController = ViewController(viewModel: viewModel)
window?.makeKeyAndVisible()

错误原因

ViewController中的private let viewModel : CompanyViewModel是非可选的存储属性,Swift要求所有非可选存储属性必须在所有初始化路径中完成初始化。当前的required init?(coder aDecoder: NSCoder)方法仅调用了父类初始化方法,未对viewModel进行赋值,因此触发编译错误。

修复方案

方案1:将viewModel改为可选类型(推荐,适配依赖注入场景)

修改ViewController中的viewModel声明及初始化逻辑:

class ViewController: UIViewController, CompanyViewModelOutput {
    // 其他代码保持不变
    private var viewModel: CompanyViewModel?
    
    init(viewModel: CompanyViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
        self.viewModel?.output = self
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView.delegate = self
        collectionView.dataSource = self
        viewModel?.fetchData()
    }
    // 其他代码保持不变
}

这种方式既保留了依赖注入的灵活性,又兼容Storyboard/XIB的初始化路径。

方案2:保证所有初始化路径都初始化viewModel(保持非可选属性)

如果希望viewModel保持非可选的let属性,可以在required init?(coder:)中初始化默认实例:

class ViewController: UIViewController, CompanyViewModelOutput {
    // 其他代码保持不变
    private let viewModel : CompanyViewModel
    
    init(viewModel: CompanyViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
        self.viewModel.output = self
    }
    
    required init?(coder aDecoder: NSCoder) {
        let apiProtocol: APIProtocol = APIManager()
        self.viewModel = CompanyViewModel(apiProtocol: apiProtocol)
        super.init(coder: aDecoder)
        self.viewModel.output = self
    }
    // 其他代码保持不变
}

注意:该方案会在通过Storyboard/XIB加载ViewController时创建新的ViewModel实例,若你统一使用SceneDelegate的依赖注入方式,需确保ViewController不会通过其他路径初始化。


内容的提问来源于stack exchange,提问作者İbrahim Bayram

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:15:07