Giới thiệu về kiến trúc Viper.
Chắc hẳn các bạn lập trình viên iOS đã rất quen thuộc với mô hình MVC được áp dụng trong iOS. Tuy nhiên, khái niệm ViewController của iOS thực sự biến việc phát triển các ứng dụng lớn, với chức năng màn hình phức tạp trở thành một mớ .... (you know what i mean). Trải qua quá trình phát triển, bảo ...
Chắc hẳn các bạn lập trình viên iOS đã rất quen thuộc với mô hình MVC được áp dụng trong iOS. Tuy nhiên, khái niệm ViewController của iOS thực sự biến việc phát triển các ứng dụng lớn, với chức năng màn hình phức tạp trở thành một mớ .... (you know what i mean). Trải qua quá trình phát triển, bảo trì, thêm thắt chức năng, ViewController dần thay đổi để trở thành một khái niệm mới : Massive ViewController với số lượng dòng code có thể lên tới hàng nghìn dòng trong một file. Và kể cả bạn có tách class tốt thế nào đi chăng nữa, việc một lập trình viên có vinh hạnh đi hót .... cho bạn sau đó cũng trở nên thực sự bốc mùi đối với họ.
Sau vài ngày nghiên cứu, tôi đã tìm thấy thứ này trên mạng. Họ gọi nó là VIPER - Một Clean Architecture cho các ứng dụng iOS. Áp dụng VIPER vào ứng dụng mới nhất tôi đang phát triển cho Framgia, hiệu quả của nó khiến tôi thấy ổn và quyết định sẽ giới thiệu VIPER tới mọi người qua bài viết này.
I.VIPER
VIPER bao gồm : VIEW - INTERACTOR - PRESENTER - ENTITY - ROUTING
VIEW : hiển thị giao diện dựa trên hướng dẫn của PRESENTER đồng thời tiếp nhận input và truyền tới PRESENTER
INTERACTOR : Chứa các business logic tùy theo use case tương ứng.
PRESENTOR : Chứa view logic để hiển thị ( nhận kết quả từ interactor), nhận user input và request tới interactor.
ENTITY : Các model object được sử dụng bởi interactor
_ ROUTING : điều khiển việc hiển thị các màn hình. _
Với Viper, chúng ta sẽ chia project thành các module tương ứng với từng use case của ứng dụng , mỗi module sẽ bao gồm các layer tương ứng như trên. Để các bạn dễ hiểu, phần tiếp theo mình sẽ xây dựng một ứng dụng đơn giản bám sát với việc xây dựng từng layer trong VIPER.
Ứng dụng của mình sẽ chỉ có một use case đơn giản : Tiến hành quá trình login
INTERACTOR:
Đầu tiên, ta sẽ define ra 2 protocol input, out put làm nhiệm vụ liên lạc giữa presenter và interactor.
protocol LoginInput { func loginInput(userName : String?, password : String?) }
protocol LoginOutput { func loginOutput(result : Bool, message : String) }
Interactor chứa bussiness logic , công việc thực hiện trên Interactor hoàn toàn không liên quan gì tới UI, và chỉ tương tác với dữ liệu. Do đó, chúng ta cũng có thể dễ dàng áp dụng TDD ( Test Driven Development) đối với việc sử dụng VIPER.
Ở đây, Interactor nhận username và password từ presenter, so sánh dữ liệu với DataStore và trả về kết quả tương ứng.
func loginInput(userName: String?, password: String?) { if userName == nil { output?.loginOutput(false, message: "Please tell me your user name!!!!") return } if password == nil { output?.loginOutput(false, message: "Please tell me your password!!!!") return } if userName != DataStore.sharedInstance.user.userName { output?.loginOutput(false, message: "User name is not correct") return } if password != DataStore.sharedInstance.user.password { output?.loginOutput(false, message: "Password is not correct") return } output?.loginOutput(true, message: "Correct!!!!") }
ENTITY
Entity chỉ được thao tác bởi Interactor, ở đây, tôi chỉ tạo ra object UserEntity và sử dụng class DataStore để lưu trữ dữ liệu tạm, tuy nhiên việc thao tác với các cơ sở dữ liệu khác như CoreData hay Sqlite cũng k có nhiều khác biệt.
PRESENTER
Trái ngược với Interactor, Presenter chủ yếu chứa các logic để điều khiển UI. Presenter nhận Input từ các interactor và update UI, đồng thời gửi request tới các interactor.
class LoginPresenter: NSObject, LoginEventHandler { var input : LoginInput? var loginVC : LoginViewController? func login(userName: String?, password: String?) { input?.loginInput(userName, password: password) } } extension LoginPresenter : LoginOutput { func loginOutput(result: Bool, message: String) { if result { // move to next screen LoginWireFrame.sharedInstance.presentSuccessViewController() } else { LoginWireFrame.sharedInstance.presentAlertForLoginView(message) } } }
Chẳng hạn trong trường hợp này, class LoginPresenter nhận thông tin từ LoginViewController thông qua delegate LoginEventHandler, và request tới Interactor bằng LoginInput. Sau khi Interactor xử lí thông tin, dữ liệu lại được update ngược tới LoginViewController bằng LoginOutput.
View
Trong trường hợp này chính là các ViewController, nhưng chúng bị động, chỉ chờ để nhận lệnh xử lí từ Presenter. Nếu như trong kiến trúc MVC quen thuộc ta vẫn làm, ViewController chứa rất nhiều logic xử lí giao diện, đôi khi cả dữ liệu thì giờ đây các phần công việc đó đều đã được Presenter và Interactor xử lý.
ROUTING
Routing chính là lớp LoginWireFrame, layer này chỉ thực hiện khởi tạo các màn hình, và điều khiển việc hiển thị, dịch chuyển các màn hình.
class LoginWireFrame: NSObject { var loginViewController : LoginViewController? class var sharedInstance : LoginWireFrame { struct Static { static let instance : LoginWireFrame = LoginWireFrame() } return Static.instance } func presentLoginViewControllerFromWindow(window : UIWindow) { // create LoginViewController instance let storyBoard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) let loginVC = storyBoard.instantiateViewControllerWithIdentifier("LoginViewController") as! LoginViewController // create presenter && interactor let loginPresenter = LoginPresenter() let loginInteractor = LoginInteractor() loginPresenter.input = loginInteractor loginPresenter.loginVC = loginViewController loginInteractor.output = loginPresenter // set event handler for loginViewController loginVC.eventHandler = loginPresenter self.loginViewController = loginVC let navController = UINavigationController(rootViewController: self.loginViewController!) window.rootViewController = navController } func presentSuccessViewController() { let storyBoard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) let successViewController = storyBoard.instantiateViewControllerWithIdentifier("SuccessViewController") as! SuccessViewController dispatch_async(dispatch_get_main_queue()) { () -> Void in self.loginViewController?.navigationController?.pushViewController( successViewController, animated: true ) } } func presentAlertForLoginView(message : String) { // show alert let alertViewController = UIAlertController( title: "Login Failed", message: message, preferredStyle: .Alert ) let okAction = UIAlertAction( title: "OK", style: .Cancel, handler: { (action : UIAlertAction) -> Void in print("OK") }) alertViewController.addAction(okAction) loginViewController?.presentViewController(alertViewController, animated: true, completion: nil) } }
Kết luận :
Mặc dù project được sử dụng kiến trúc Viper có nhược điểm là nó chứa nhiều class hơn so với cách thông thường. Tuy nhiên giờ đây bạn sẽ không còn gặp phải các class quá lớn và phức tạp như trước kia. Ứng dụng trở nên mạch lạc hơn do các use case được chia ra rõ ràng. Điều quan trọng hơn là ta có thể dễ dàng viết Test hoặc triển khai TDD.
Các bạn có thể tải sample mẫu ở đây :
https://drive.google.com/file/d/0B9FOeas3cAbIeXRnTUVPUmZUaUE/view
Chúc các bạn vui vẻ.