12/08/2018, 16:42

iOS Architecture Patterns

Cảm thấy lạ khi làm MVC trong iOS? Có nghi ngờ về việc chuyển sang MVVM? Nghe nói về VIPER, nhưng không chắc nó có đáng không? Tiếp tục đọc, và bạn sẽ tìm thấy câu trả lời cho các câu hỏi ở trên, nếu bạn cảm thấy không thoải mái thì có thể đóng góp ý kiến ở phần bình luận. Bạn sắp sắp xếp kiến ...

Cảm thấy lạ khi làm MVC trong iOS? Có nghi ngờ về việc chuyển sang MVVM? Nghe nói về VIPER, nhưng không chắc nó có đáng không?

Tiếp tục đọc, và bạn sẽ tìm thấy câu trả lời cho các câu hỏi ở trên, nếu bạn cảm thấy không thoải mái thì có thể đóng góp ý kiến ở phần bình luận.

Bạn sắp sắp xếp kiến thức về các mẫu kiến trúc trong môi trường iOS. Chúng tôi sẽ xem xét một số ngắn gọn một số phổ biến và so sánh chúng về lý thuyết và thực hành đi qua một vài ví dụ nhỏ. Theo các liên kết nếu bạn cần thêm chi tiết về bất kỳ trường hợp cụ thể nào.

Thử nghiệm các mẫu thiết kế có thể gây nghiện, vì vậy hãy cẩn thận: bạn có thể tự hỏi mình nhiều câu hỏi hơn trước khi đọc bài viết này như sau:

Ai được yêu cầu có yêu cầu kết nối mạng: một Model hay một Controller?

Làm cách nào để chuyển Model thành một View-Model của View mới?

Ai tạo ra mô-đun VIPER mới: Router hoặc Presenter?

Tại sao lại quan tâm đến việc chọn kiến trúc?

Bởi vì nếu không, một ngày nào đó, gỡ lỗi một Class khổng lồ với hàng chục thứ khác nhau, bạn sẽ thấy mình không thể tìm và sửa bất kỳ lỗi nào trong Class của mình. " Đương nhiên, thật khó để giữ cho Class này trong tâm trí là toàn bộ thực thể, do đó, bạn sẽ luôn thiếu một số chi tiết quan trọng. Nếu bạn đang trong tình huống này với đơn của bạn, rất có thể là:

  • Class này là một subclass của UIViewController
  • Dữ liệu của bạn được lưu trữ trực tiếp trong UIViewController
  • Các UIView của bạn hầu như không làm gì
  • Model là một kiến trúc dummy data
  • Unit test không được bao gồm trong kiến trúc

Mô hình MVC của Apple, cảm thấy không được tốt. Có một điều gì đó sai trái với MVC của Apple, nhưng chúng tôi sẽ quay lại sau.

Hãy xác định các tính năng của một kiến trúc tốt:

  • Phân bố cân bằng các trách nhiệm giữa các thực thể có vai trò nghiêm ngặt.
  • Khả năng kiểm tra thường xuất phát từ tính năng đầu tiên (và đừng lo lắng: nó dễ dàng với kiến trúc thích hợp).
  • Dễ sử dụng và chi phí bảo trì thấp.

Tại sao phân phối?

Sự phân bố giữ được sự cân bằng trong bộ não của chúng ta trong khi chúng ta cố gắng tìm ra cách mọi thứ hoạt động. Nếu bạn nghĩ rằng bạn càng phát triển tốt hơn não của bạn sẽ thích ứng với sự hiểu biết sự phức tạp, thì bạn là đúng. Nhưng khả năng này không quy mô tuyến tính và đạt đến nắp rất nhanh chóng. Vì vậy, cách đơn giản nhất để đánh bại sự phức tạp là phân chia trách nhiệm giữa nhiều thực thể theo nguyên tắc trách nhiệm duy nhất.

Tại sao kiểm tra?

Điều này thường không phải là một câu hỏi cho những người đã cảm thấy biết ơn các bài kiểm tra đơn vị, mà không thành công sau khi thêm các tính năng mới hoặc do refactoring một số phức tạp của lớp. Điều này có nghĩa là các bài kiểm tra đã cứu các nhà phát triển khỏi việc tìm ra các vấn đề trong thời gian chạy, điều này có thể xảy ra khi ứng dụng trên thiết bị của người dùng và việc sửa chữa mất một tuần để tiếp cận người dùng.

Tại sao dễ sử dụng?

Điều này không đòi hỏi một câu trả lời nhưng nó là giá trị đề cập đến mã tốt nhất là mã mà chưa bao giờ được viết. Do đó ít mã bạn có, ít lỗi bạn có. Điều này có nghĩa là mong muốn viết mã ít hơn không bao giờ được giải thích chỉ bởi sự lười biếng của nhà phát triển và bạn không nên ưa một giải pháp thông minh hơn nhắm mắt để chi phí bảo trì.

Thông tin cần thiết về MV (X)

Ngày nay chúng ta có nhiều lựa chọn khi nói đến các mẫu thiết kế kiến trúc:

  • MVC
  • MVP
  • MVVM
  • VIPER

Đầu tiên trong ba số họ giả định đặt các thực thể của ứng dụng vào một trong ba loại:

  • Models - chịu trách nhiệm về dữ liệu tên miền hoặc lớp truy cập dữ liệu thao tác dữ liệu, hãy nghĩ đến các lớp 'Person' hoặc 'PersonDataProvider'.

  • Views - chịu trách nhiệm về lớp trình bày (GUI), đối với môi trường iOS nghĩ đến mọi thứ bắt đầu với tiền tố 'UI'.

  • Controller / Presenter / ViewModel - kết hoặc hòa giải giữa Model và View, nói chung có trách nhiệm thay đổi Model bằng phản ứng với các hành động của người dùng được thực hiện trên View và cập nhật View với những thay đổi từ Model.

Có các đơn vị được chia cho phép chúng tôi:

  • Hiểu họ tốt hơn (như chúng ta đã biết)

  • Tái sử dụng chúng (chủ yếu áp dụng cho View và Model)

  • Kiểm tra chúng một cách độc lập

Hãy bắt đầu với mẫu MV (X) và quay lại VIPER sau.

MVC

Sử dụng nó như thế nào

Trước khi thảo luận tầm nhìn của Apple về MVC, chúng ta hãy cùng nhìn vào cái nhìn truyền thống của Apple.

Trong trường hợp này, View là trạng thái chưa được xác định. Nó chỉ đơn giản là rendered bởi Controller khi Model được thay đổi. Hãy suy nghĩ về trang web hoàn toàn được tải lại một khi bạn nhấn vào liên kết để điều hướng ở một nơi khác. Mặc dù có thể thực hiện ứng dụng MVC truyền thống trong ứng dụng iOS, nhưng nó không có ý nghĩa nhiều do vấn đề kiến trúc - tất cả ba thực thể đều được kết hợp chặt chẽ, mỗi thực thể đều biết về hai ứng dụng kia. Điều này làm giảm đáng kể khả năng sử dụng lại của mỗi người - đó không phải là điều bạn muốn có trong ứng dụng của bạn. Vì lý do này, chúng ta bỏ qua cả việc cố gắng viết một ví dụ MVC chuẩn.

MVC truyền thống không có vẻ phù hợp với phát triển iOS hiện đại.

Apple’s MVC

Sự mong đợi

Controlelr là trung gian giữa View và Model để họ không biết về nhau. Controller ít nhất có thể sử dụng lại được và điều này thường rất tốt đối với chúng tôi vì chúng ta phải có một chỗ cho tất cả những logic kinh doanh khôn lanh không phù hợp với Model.

Theo lý thuyết, nó có vẻ rất đơn giản, nhưng bạn cảm thấy có điều gì đó sai, phải không? Bạn thậm chí còn nghe người ta không viết tắt MVC như Massive View Controller. Hơn nữa, điều khiển bộ tải đã trở thành một chủ đề quan trọng cho các nhà phát triển iOS. Tại sao điều này xảy ra nếu Apple chỉ lấy MVC truyền thống và cải tiến nó một chút?

Apple’s MVC

Thực tế

Cocoa MVC khuyến khích bạn viết Massive View Controllers, bởi vì chúng cũng tham gia vào vòng đời của View mà thật khó để nói chúng tách biệt. Mặc dù bạn vẫn có khả năng giảm tải một số logic nghiệp vụ và chuyển đổi dữ liệu sang Model, nhưng bạn không có nhiều sự lựa chọn khi thực hiện việc tải xuống chế độ View, phần lớn thời gian trách nhiệm của View là gửi hành động đến Controller. View Controller ở điểm kết thúc là một đại biểu và một nguồn dữ liệu của tất cả mọi thứ, và thường chịu trách nhiệm cho việc gửi và hủy các yêu cầu mạng và ... bạn đặt tên nó.

Đã bao giờ bạn thấy mã như thế này:

var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)

Các cells, đó là View được cấu hình trực tiếp với Model, do đó, do vậy MVC bị vi phạm, nhưng điều này xảy ra mọi lúc, và thường mọi người không cảm thấy đó là sai. Nếu bạn thực hiện đúng theo MVC, thì bạn nên cấu hình các cell từ Controller, và không vượt qua Model vào View, và điều này sẽ làm tăng kích thước của Controller của bạn nhiều hơn.

Cocoa MVC được viết tắt là hợp lý không giống như Massive View Controller.

Vấn đề có thể không được rõ ràng cho đến khi Unit test (hy vọng, nó trong dự án của bạn). Vì view controller của bạn được kết hợp chặt chẽ với chế độ xem nên việc kiểm tra trở nên khó khăn vì bạn phải sáng tạo trong chế độ xem và chu kỳ sống của mình, trong khi viết mã trình điều khiển chế độ theo cách như vậy, logic kinh doanh của bạn được tách ra nhiều càng tốt từ mã bố cục của chế độ xem.

Chúng ta hãy cùng xem xét ví dụ trên playground đơn giản:

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

class GreetingViewController : UIViewController { // View + Controller
    var person: Person!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.greetingLabel.text = greeting
        
    }
    // layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

MVC lắp ráp có thể được thực hiện trong bộ điều khiển hiển thị hiện nay

Điều này dường như không kiểm chứng được, đúng không? Chúng ta có thể di chuyển thế hệ chào mừng vào lớp GreetingModel mới và kiểm tra nó một cách riêng biệt, nhưng chúng ta không thể kiểm tra bất kỳ logic trình bày nào (mặc dù không có logic như trong ví dụ ở trên) bên trong GreetingViewController mà không gọi trực tiếp các phương thức liên quan tới UIView viewDidLoad, didTapButton) có thể gây ra việc tải tất cả các chế độ xem, và điều này là không tốt cho việc kiểm tra đơn vị.

Trên thực tế, tải và thử nghiệm UIViews trên một trình mô phỏng (ví dụ iPhone 4S) không đảm bảo rằng nó sẽ hoạt động tốt trên các thiết bị khác (ví dụ như iPad), vì vậy chúng tôi khuyên bạn nên gỡ bỏ "Host Application" khỏi cấu hình mục tiêu và chạy thử nghiệm của bạn mà không có ứng dụng của bạn chạy trên giả lập.

Sự tương tác giữa View và Controller không thực sự kiểm chứng được với Unit Tests

Với tất cả những gì đã nói, có vẻ như Cocoa MVC là một mô hình khá xấu để lựa chọn. Nhưng chúng ta hãy đánh giá nó theo các tính năng được xác định trong phần đầu của bài viết:

  • Phân phối - View và Model trên thực tế tách ra, nhưng View và Controller được kết hợp chặt chẽ.

  • Khả năng kiểm tra - do phân phối không tốt, có thể bạn chỉ thử nghiệm Model của bạn.

  • Dễ sử dụng - ít nhất là số lượng mã trong số các mẫu khác. Ngoài ra mọi người đều quen thuộc với nó, do đó, nó được duy trì dễ dàng ngay cả bởi các nhà phát triển không có kinh nghiệm.

Cocoa MVC là mẫu của bạn lựa chọn nếu bạn không sẵn sàng đầu tư thêm thời gian vào kiến trúc của bạn, và bạn cảm thấy rằng một cái gì đó với chi phí bảo trì cao hơn là quá mức cần thiết cho dự án thú cưng nhỏ bé của bạn.

Cocoa MVC is the best architectural pattern in terms of the speed of the development.

MVP

Cocoa MVC hứa hẹn được phân phối

Nó không giống chính xác như MVC của Apple? Vâng, nó có, và đó là tên MVP (Passive View variant). Nhưng chờ một phút ... Điều này có nghĩa là MVC của Apple là trên thực tế là một MVP? Không, nó không phải, bởi vì nếu bạn nhớ lại, ở đó, View được kết hợp chặt chẽ với Controller, trong khi trình trung gian của MVP, Presenter, không liên quan gì đến vòng đời của bộ điều khiển chế độ xem, và View có thể bị chế nhạo một cách dễ dàng, do đó không có mã bố trí trong Trình trình bày nào cả, nhưng nó có trách nhiệm cập nhật Chế độ xem với dữ liệu và trạng thái.

Điều gì sẽ xảy ra nếu tôi nói với bạn, UIViewController là View.

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}

class GreetingPresenter : GreetingViewPresenter {
    unowned let view: GreetingView
    let person: Person
    required init(view: GreetingView, person: Person) {
        self.view = view
        self.person = person
    }
    func showGreeting() {
        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var presenter: GreetingViewPresenter!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.presenter.showGreeting()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

Lưu ý quan trọng về lắp ráp

MVP là mô hình đầu tiên cho thấy vấn đề lắp ráp xảy ra do có ba lớp thực sự riêng biệt. Vì chúng ta không muốn View để biết về Model, nên không thể thực hiện assembly trong bộ điều khiển view controller (đó là View), do đó chúng ta phải làm điều đó ở một nơi khác. Ví dụ, chúng ta có thể làm cho các dịch vụ Router rộng ứng dụng sẽ chịu trách nhiệm thực hiện lắp ráp và trình bày View-to-View. Vấn đề này phát sinh và phải được giải quyết không chỉ trong MVP mà còn trong tất cả các mô hình sau đây.

Chúng ta hãy nhìn vào các tính năng của MVP:

  • Phân phối - chúng tôi có hầu hết các trách nhiệm được chia giữa Presenter và Model, với chế độ Dumb View (trong ví dụ ở trên Model cũng ngớ ngẩn).

  • Testability - là tuyệt vời, chúng ta có thể kiểm tra hầu hết các logic kinh doanh do dumb View.

  • Dễ dàng sử dụng - trong ví dụ đơn giản không thực tế của chúng tôi, số lượng mã được nhân đôi so với MVC, nhưng đồng thời, ý tưởng của MVP là rất rõ ràng.

MVP trong iOS có nghĩa là khả năng kiểm tra tuyệt vời và rất nhiều mã.

MVP

Với Bindings và Hooters

Có một hương vị khác của MVP - MVP giám sát điều khiển. Phiên bản này bao gồm việc ràng buộc trực tiếp View và Model trong khi Presenter (The Supervising Controller) vẫn xử lý các hành động từ View và có khả năng thay đổi View.

Nhưng như chúng ta đã biết trước đây, sự tách biệt trách nhiệm mơ hồ là không tốt, cũng như sự kết hợp chặt chẽ của View và Model. Điều này cũng tương tự như cách mọi thứ hoạt động trong phát triển máy tính để bàn Cocoa.

Giống như với MVC truyền thống, tôi không thấy một điểm bằng văn bản một ví dụ cho kiến trúc thiếu sót.

MVVM

Loại mới nhất và lớn nhất của loại MV (X)

MVVM là phiên bản mới nhất của MV (X) vì vậy, chúng ta hãy hy vọng nó xuất hiện với những vấn đề mà MV (X) đã phải đối mặt trước đây.

Về lý thuyết Model-View-ViewModel trông rất tốt. Chế độ xem và Mô hình đã quen thuộc với chúng tôi, nhưng cũng là Người trung gian, được trình bày dưới dạng View-Model.

Nó khá giống với MVP:

  • MVVM xử lý bộ điều khiển chế độ xem dưới dạng View

  • Không có sự kết hợp chặt chẽ giữa View và Model

Ngoài ra, nó có tính ràng buộc giống như phiên bản Giám sát của MVP; tuy nhiên, lần này không nằm giữa View và Model, nhưng giữa View và View-Model.

Vì vậy, View Model trong thực tế iOS là gì? Nó là cơ bản UIKit đại diện độc lập của View của bạn và nhà nước của nó. View Model mô phỏng các thay đổi trong Model và tự cập nhật bằng Model cập nhật và vì chúng tôi có ràng buộc giữa View và View Model, nên người đầu tiên được cập nhật cho phù hợp.

Bindings

Tôi ngắn gọn đề cập đến chúng trong phần MVP, nhưng chúng ta hãy thảo luận về chúng một chút ở đây. Các liên kết đi ra khỏi hộp để phát triển OS X, nhưng chúng tôi không có chúng trong hộp công cụ iOS. Tất nhiên chúng tôi có KVO và thông báo, nhưng họ không phải là thuận tiện như bindings.

Vì vậy, với điều kiện chúng ta không muốn viết chúng ta, chúng ta có hai lựa chọn:

  • Một trong những thư viện ràng buộc dựa trên KVO như RZDataBinding hoặc SwiftBond

  • Các chương trình hoạt động đầy đủ chức năng hoạt động phản ứng như ReactiveCocoa, RxSwift hoặc PromiseKit.

Trên thực tế, hiện nay, nếu bạn nghe "MVVM" - bạn nghĩ rằng ReactiveCocoa, và ngược lại. Mặc dù có thể xây dựng MVVM với các ràng buộc đơn giản, ReactiveCocoa (hoặc anh chị em ruột) sẽ cho phép bạn nhận được hầu hết các MVVM.

Có một sự thật cay đắng về các reactive frameworks: sức mạnh to lớn đi kèm với trách nhiệm to lớn. Thật sự dễ dàng để gây rối cho mọi thứ khi bạn phản ứng lại. Nói cách khác, nếu bạn làm điều gì đó sai, bạn có thể dành nhiều thời gian để gỡ lỗi ứng dụng, do đó, chỉ cần nhìn vào ngăn xếp cuộc gọi này.

Trong ví dụ đơn giản của chúng tôi, khung FRF hoặc thậm chí KVO là một sự sử dụng quá mức, thay vào đó chúng ta sẽ yêu cầu mô hình View để cập nhật bằng cách sử dụng phương thức showGreeting và sử dụng thuộc tính đơn giản cho hàm greetingDidChange callback.

import UIKit

struct Person { // Model
    let firstName: String
    let lastName: String
}

protocol GreetingViewModelProtocol: class {
    var greeting: String? { get }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
    init(person: Person)
    func showGreeting()
}

class GreetingViewModel : GreetingViewModelProtocol {
    let person: Person
    var greeting: String? {
        didSet {
            self.greetingDidChange?(self)
        }
    }
    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
    required init(person: Person) {
        self.person = person
    }
    func showGreeting() {
        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
    }
}

class GreetingViewController : UIViewController {
    var viewModel: GreetingViewModelProtocol! {
        didSet {
            self.viewModel.greetingDidChange = { [unowned self] viewModel in
                self.greetingLabel.text = viewModel.greeting
            }
        }
    }
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)
    }
    // layout code goes here
}
// Assembling of MVVM
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel

Và một lần nữa trở lại đánh giá tính năng của chúng tôi:

  • Phân phối - không rõ ràng trong ví dụ nhỏ của chúng ta, nhưng trên thực tế, View của MVVM có nhiều trách nhiệm hơn View của MVP. Bởi vì người đầu tiên cập nhật trạng thái của nó từ View Model bằng cách thiết lập các ràng buộc, khi phần thứ hai chỉ chuyển tiếp tất cả các sự kiện sang Presenter và không cập nhật chính nó.

  • Khả năng kiểm tra - mô hình View không biết gì về View, điều này cho phép chúng ta kiểm tra nó một cách dễ dàng. View cũng có thể được kiểm tra, nhưng vì nó là UIKit phụ thuộc bạn có thể muốn bỏ qua nó.

  • Dễ sử dụng - nó có cùng số lượng mã như MVP trong ví dụ của chúng ta, nhưng trong ứng dụng thực mà bạn phải chuyển tất cả các sự kiện từ View sang Presenter và để cập nhật View bằng tay, MVVM sẽ có nhiều skinnier nếu bạn sử dụng kết hợp.

MVVM rất hấp dẫn, vì nó kết hợp các lợi ích của các cách tiếp cận nói trên, và thêm vào đó, nó không đòi hỏi phải có mã bổ sung cho các cập nhật View do các ràng buộc ở phía bên xem. Tuy nhiên, testability vẫn ở mức tốt.

VIPER

Trải nghiệm xây dựng LEGO được chuyển vào thiết kế ứng dụng iOS

VIPER là ứng cử viên cuối cùng của chúng tôi, đặc biệt thú vị bởi vì nó không đến từ thể loại MV (X).

Bây giờ, bạn phải đồng ý rằng mức độ chi tiết trong trách nhiệm rất tốt. VIPER lặp lại một lần nữa ý tưởng tách trách nhiệm, và lần này chúng ta có năm lớp.

  • Interactor - chứa logic kinh doanh liên quan đến dữ liệu (Entities) hoặc mạng, như tạo các thể hiện mới của thực thể hoặc lấy chúng từ máy chủ. Đối với những mục đích đó, bạn sẽ sử dụng một số Dịch vụ và Người quản lý không được coi là một phần của mô-đun VIPER mà là một sự phụ thuộc bên ngoài.

  • Presenter - chứa logic kinh doanh liên quan đến UI (nhưng là UIKit độc lập), gọi phương thức trên Interactor.

  • Entities - đối tượng dữ liệu của bạn không phải là lớp truy cập dữ liệu, bởi vì đó là trách nhiệm của Interactor.

  • Router - chịu trách nhiệm về việc chia giữa các mô-đun VIPER.

Về cơ bản, mô-đun VIPER có thể là một màn hình hoặc toàn bộ câu chuyện về người dùng về ứng dụng của bạn - hãy nghĩ đến xác thực, có thể là một màn hình hoặc một số liên quan. Khối "LEGO" của bạn nhỏ đến mức nào? - Tuỳ bạn.

Nếu so sánh nó với loại MV (X), chúng ta sẽ thấy một vài khác biệt về phân bố trách nhiệm:

  • Model (tương tác dữ liệu) đã chuyển vào Interactor với các thực thể như các cấu trúc dữ liệu câm.

  • Chỉ có các nhiệm vụ biểu diễn UI của Controller / Presenter / ViewModel đã chuyển vào Presenter, nhưng không phải là các khả năng thay đổi dữ liệu.

  • VIPER là mô hình đầu tiên nêu rõ trách nhiệm dẫn đường, được Router giải quyết.

Cách thực hiện đúng cách định tuyến là một thách thức đối với các ứng dụng iOS, các mô hình MV (X) đơn giản không giải quyết vấn đề này.

Ví dụ không bao gồm việc định tuyến hoặc tương tác giữa các mô-đun, vì các chủ đề này không được bao phủ bởi các mẫu MV (X).

import UIKit

struct Person { // Entity (usually more complex e.g. NSManagedObject)
    let firstName: String
    let lastName: String
}

struct GreetingData { // Transport data structure (not Entity)
    let greeting: String
    let subject: String
}

protocol GreetingProvider {
    func provideGreetingData()
}

protocol GreetingOutput: class {
    func receiveGreetingData(greetingData: GreetingData)
}

class GreetingInteractor : GreetingProvider {
    weak var output: GreetingOutput!
    
    func provideGreetingData() {
        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
        let subject = person.firstName + " " + person.lastName
        let greeting = GreetingData(greeting: "Hello", subject: subject)
        self.output.receiveGreetingData(greeting)
    }
}

protocol GreetingViewEventHandler {
    func didTapShowGreetingButton()
}

protocol GreetingView: class {
    func setGreeting(greeting: String)
}

class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
    weak var view: GreetingView!
    var greetingProvider: GreetingProvider!
    
    func didTapShowGreetingButton() {
        self.greetingProvider.provideGreetingData()
    }
    
    func receiveGreetingData(greetingData: GreetingData) {
        let greeting = greetingData.greeting + " " + greetingData.subject
        self.view.setGreeting(greeting)
    }
}

class GreetingViewController : UIViewController, GreetingView {
    var eventHandler: GreetingViewEventHandler!
    let showGreetingButton = UIButton()
    let greetingLabel = UILabel()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
    }
    
    func didTapButton(button: UIButton) {
        self.eventHandler.didTapShowGreetingButton()
    }
    
    func setGreeting(greeting: String) {
        self.greetingLabel.text = greeting
    }
    
    // layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter

Một lần nữa, trở lại với các tính năng:

  • Phân phối - chắc chắn, VIPER là một nhà phân phối vô địch về trách nhiệm.

  • Khả năng kiểm tra-không có gì ngạc nhiên ở đây, phân phối tốt hơn - testability tốt hơn.

  • Dễ dàng sử dụng - cuối cùng, hai ở trên có chi phí bảo trì như bạn đã đoán. Bạn phải viết một lượng lớn giao diện cho các lớp có trách nhiệm rất nhỏ.

Vậy còn LEGO thì sao?

Trong khi sử dụng VIPER, bạn có thể cảm thấy như đang xây dựng tòa nhà Empire State Building từ khối LEGO và đó là tín hiệu cho thấy bạn gặp vấn đề. Có thể, còn quá sớm để chấp nhận VIPER cho ứng dụng của bạn và bạn nên xem xét một cái gì đó đơn giản hơn. Một số người bỏ qua điều này và tiếp tục bắn ra khỏi khẩu pháo vào chim sẻ. Tôi cho rằng họ tin rằng các ứng dụng của họ sẽ có lợi từ VIPER ít nhất trong tương lai, ngay cả khi bây giờ chi phí bảo trì là cao bất hợp lý. Nếu bạn tin như vậy, sau đó tôi khuyên bạn nên thử Generamba - một công cụ để tạo ra bộ xương VIPER. Mặc dù đối với cá nhân tôi, nó cảm thấy như sử dụng một hệ thống nhắm mục tiêu tự động cho pháo thay vì chỉ đơn giản là chụp một shot sling.

Phần kết luận

Chúng tôi đã đi theo một vài mẫu kiến trúc, và tôi hy vọng bạn đã tìm ra câu trả lời cho những gì làm phiền bạn, nhưng tôi không nghi ngờ gì khi bạn nhận ra rằng không có viên đạn bạc nên chọn mô hình kiến trúc là một vấn đề cân bằng trọng lượng trong tình huống cụ thể của bạn.

Do đó, tự nhiên là có một sự kết hợp của kiến trúc trong cùng một ứng dụng. Ví dụ: bạn đã bắt đầu với MVC, sau đó bạn nhận ra rằng một màn hình cụ thể trở nên quá khó để duy trì hiệu quả với MVC và chuyển sang MVVM, nhưng chỉ dành cho màn hình đặc biệt này. Không cần refactor màn hình khác mà MVC thực sự làm việc tốt, bởi vì cả hai kiến trúc dễ dàng tương thích.

0