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
相关产品推荐
相关产品推荐

