如何在Swift中重新构造代码以遵循MVVM设计模式?

时间:2018-02-01 06:17:37

标签: ios swift mvvm

在我的代码中,我一直在使用苹果公司在他们的演示中展示的MVC模式。但是,我试图通过遵循MVVM模式来减少膨胀的UIViewController来回避编写更清晰,更健壮的代码:

我对它的理解是ControllerViewModel一无所知,但与中间人ViewModel进行通信,中间人Model存储有关Controller并将其传递给View,后者又会更新Model

我已将代码重新构建为ViewModelstruct PersonModel { public var name: String public var position: String public var imageLink: String public var listOrder: Int init() { self.name = "Unknown" self.position = "Unknown" self.imageLink = "url link" self.listOrder = 0 } init(name: String, position: String, imageLink: String, listOrder: Int) { self.name = name self.position = position self.imageLink = imageLink self.listOrder = listOrder } } class PersonViewModel { fileprivate var personModel: PersonModel public var name: String { return personModel.name } public var position: String { return personModel.position } public var imageLink: String { return personModel.imageLink } public var listOrder: Int { return personModel.listOrder } init(personModel: PersonModel) { self.personModel = personModel } } ,如下所示:

UIViewController

这看起来很简单。

我目前遇到的问题是清除当前位于我的PersonModel中的其他代码,这些代码会执行一些代码以检索表示{{1}的 JSON 对象},并验证其数据内容。

var personInfo = [PersonModel]()

func retrieveFromDatabase()
{
    let json = JSON()

    json.getJSONData(link: "database link") { (json) in
        if json.isEmpty == false
        {
            self.storeJSONData(json: json)
        }
    }
}

func storeJSONData(json: [String : Any])
{
    for key in json.keys.sorted()
    {
        // Store each person information
        guard let info = json[key] as? [String: Any] else {
            return
        }

        let name = retrieveProperty(json: info,
                                    property: "Name Field")
        let position = retrieveProperty(json: info,
                                        property: "Position Field")
        let listOrder = retrieveProperty(json: info,
                                         property: "List Order Field")
        let imageLink = retrieveProperty(json: info,
                                         property: "Image Link Field")

        let person = checkData(name: name,
                               listOrder: Int(listOrder)!,
                               position: position,
                               imageLink: imageLink)

        personInfo.append(person)
    }
}

func retrieveProperty(json: [String : Any], property: String) -> String
{
    guard let attribute = json[property] as? String else {
        return ""
    }

    return attribute
}

func checkData(name: String, listOrder: Int, position: String, imageLink: String) -> PersonModel
{
    var person = PersonModel()
    person.listOrder = listOrder

    if name.isEmpty == false
    {
        person.name = name
    }

    if position.isEmpty == false
    {
        person.position = position
    }

    if imageLink.isEmpty == false
    {
        person.imageLink = imageLink
    }

    return person
}

func getPersonInfo(sectionRow: Int) -> PersonModel
{
    let person = PersonModel(name: personInfo[sectionRow].name,
                           position: personInfo[sectionRow].position,
                           imageLink: personInfo[sectionRow].imageLink,
                           listOrder: personInfo[sectionRow].listOrder)

    return person
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCell",
                                                        for: indexPath) as! CustomCell

    let personModel = getPersonInfo(sectionRow: indexPath.row)

    guard let url = URL(string: personModel.imageLink) else {
        return UICollectionViewCell()
    }
    let imageResouce = ImageResource(downloadURL: url,
                                     cacheKey: personModel.imageLink)

    cell.staffImageView.kf.setImage(with: imageResouce,
                                    placeholder: #imageLiteral(resourceName: "default_profile"),
                                    options: [.transition( .fade(0.3) ),
                                              .fromMemoryCacheOrRefresh]
    )
    cell.nameLabel.text = personModel.name
    cell.positionLabel.text = personModel.position

    return cell
}
  1. 要遵循MVVM模式,上述 JSON 代码是否会迁移到PersonViewModel,而不是当前驻留在Controller
  2. 在我的Controller课程中,我需要在函数调用PersonModelView存储所有JSON数据后访问包含PersonModel数据的storeJSONData数组。如何正确构建我的代码,以便我的Controller可以在仍然遵守MVVM模式的同时访问它?
  3. 请原谅我,因为我是新手学习这种模式,并希望得到一些指导。

    谢谢!

2 个答案:

答案 0 :(得分:1)

  

要遵循MVVM模式,上述 JSON 代码是否会迁移到PersonViewModel而不是当前驻留在Controller中?

我认为,MVVM模式全部都是关于"前端"移动应用的一部分。由于viewModel并不真正了解任何UIKit事物,因此它也不应该知道任何类型的"如何将JSON转换为模型","如何处理缓存"或者"创建alamofire请求"。这部分是"后端",这就是为什么应该在这里引入不同的特定类(例如,你可以称它们为service)。

  

在我的Controller课程中,我需要在函数调用PersonModelView存储所有JSON数据后访问包含PersonModel数据的storeJSONData数组。如何正确构建我的代码,以便我的Controller可以在仍然遵守MVVM模式的同时访问它?

注意:也许您不应该创建包含PersonModelView"的PersonModel数组,但只创建一个viewModel包含模型数组?

根据最高评论,我这样处理:

  • Controller viewDidLaod方法中创建viewModel;
  • viewModel中创建任何configure initializeservice func,可以从您特定的Controller中找回模型;
  • 最后,通知closures所有存储的数据。为此,您可以使用red或更具体的工具,例如PromiseKitgoogle promises),RxSwift等。

这是我个人试图遵循的几件事。

有点深入解释。

enter image description here

图例:green行向下移动以获取数据; configure个数据将数据带到顶部(UI)。

所以,正如我所提到的,getPersonsPersonService方法调用getPersons PersonModel方法来检索ViewModel(这很重要! JSON,字符串数组或任何其他简单类型 - 我们需要返回最终模型或错误(nil))。怎么回事? lazy var personService: PersonServiceProtocol = { let personParser: PersonParserProtocol = PersonParser() let personValidator: PersonValidationProtocol = PersonValidator() return PersonService(parser: parser, validator: validator) }() 有财产

parser

管理所有"后端"适合我们的事情。此外,还有一件棘手的事情:我们在validator中设置了initPersonDB,但PersonService已经存储在Person中。为什么?发生这种情况,因为将来您可能会有PersonService应该检索到的parser种不同类型的validator。对于此类工作,您需要为不同类型的Person手动设置DBPerson,但您真的不需要为每种类型的PersonDB创建PersonService JSON

parser成功检索数据(JSON)并将其返回validation时(步骤4),您需要在closures personDb.getPersons { result in if case let .success(json) = result, self.validator.validate(json) { let models = self.parser.parse(json) completion(.success(models)) } else { // error`enter code here` } } 和{{1}下拨打电话} funcs。在这里返回models非常常见(或Promise,Observable,如果你使用PromiseKit或RxSwift,因为在这种情况下你可以用链式模式编写代码),即

viewModel

现在您有最终viewModel,您可以将其传回personService.getPerson()或应用任何修改(例如,按部分拆分),然后返回PersonDB(步骤5)。

有时它在这里看起来有点过度编码,因为你基本上可以跳过viewModel func并使用personService.getPerson()中的viewModel,解析器和验证器手动工作(即你使用Pod }只是为了调用另一个函数),但我认为迟早你会以大量视图模型结束,这也会打破你 public boolean checkNetworkConnection() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); boolean isConnected = false; if (networkInfo != null && (isConnected = networkInfo.isConnected())) { // show "Connected" & type of network "WIFI or MOBILE" tvIsConnected.setText("Connected "+networkInfo.getTypeName()); // change background color to red tvIsConnected.setBackgroundColor(0xFF7CCC26); } else { // show "Not Connected" tvIsConnected.setText("Not Connected"); // change background color to green tvIsConnected.setBackgroundColor(0xFFFF0000); } return isConnected; } 的单一责任。

这是非常简单的架构。它可以工作,但你应该仔细考虑它的所有部分:也许你可以委托给.spec.revisionHistoryLimit左右,也许某些部分(因为工作量很小)可以合并在一起。祝你好运。

答案 1 :(得分:0)

  

要遵循MVVM模式,上述JSON代码是否会迁移到PersonViewModel而不是当前驻留在Controller中?

我在使用MVVM时发现,将执行数据查询/操作的逻辑移动到DAO(数据访问对象)要容易得多,然后相关的ViewModel调用该对象来获取数据。这抽象出逻辑,以便其他ViewModel可以重用它。

注意:不要让DAO成为单身人士,而不是良好的做法。

  

在我的Controller类中,我需要访问PersonModelView数组   在所有JSON数据之后包含我的PersonModel数据   存储在函数调用storeJSONData中。我该怎么办呢   构造我的代码,以便我的控制器可以在仍然访问它时   坚持MVVM模式?

在我看来,最好对代表这样做,让ViewController成为ViewModel的委托,并将数据(在本例中为数组)从ViewModel传递给ViewController,例如。

struct PersonModel {
    // Model properties here
}

class PersonViewModel {

    weak var delegate: PersonViewModelDelegate?

    public func getPersons() -> [PersonViewModel] {

        // perform logic to get person array then return it to the delegate
        let persons = [PersonViewModel]
        delegate?.didGetPersons(persons: persons)
    }
}

protocol PersonViewModelDelegate {
    func didGetPersons(persons:[PersonViewModel])
}

class YourViewController:UIViewController {

    weak var personViewModel: PersonViewModel = PersonViewModel() {

        personViewModel.delegate = self
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // your viewcontroller code goes here
}

extension YourViewController: PersonViewModelDelegate {


    func didGetPersons(persons: [PersonViewModel]) {
        // use persons array here
    }
}