Architecting iOS Apps with VIPER

Kiến trúc VIPER

Với mỗi lập trình viên thì mô hình MVC rất quen thuộc và được áp dụng rất nhiều trong iOS trước đây và bây giờ. Tuy vậy, khi dự án của bạn quá lớn hoặc quá phức tạp thì ViewController trở thành một đống..., một thứ hỗn độn. Hơn nữa trải qua quá trình phát triển, bảo trì, thêm bớt chức năng, đặc biệt công việc đó lại là một người khác thực hiện thì ViewController sẽ trở thành MassiveViewController thì đúng hơn. Với một ViewController có thể lên tới hàng chục nghìn dòng code. Và điều đó thực sự gây khó chịu cho người tiếp theo tiếp nhận source code của bạn.

Tôi - cũng đã từng tạo ra các MassiveViewController. Hôm nay tôi quyết định giới thiệu tới các bạn mô hình, một mô hình cũng thực sự rất mạnh trong iOS và hiện tại ngày càng được áp dụng phổ biến trong các ứng dụng iOS. Mô hình đó có tên VIPER. Tôi cũng biết đến mô hình từ cách đây vài tháng trong dự án về việc làm của Nhật. Và tôi thấy thực sự nên biết với những lập trình viên iOS chưa biết về nó.

VIPER là gì?

VIPER là một kiến trúc rất rõ ràng cho các ứng dụng iOS. VIPER là viết tắt: View, Interactor, Presneter, Entity, Routing. Kiến trúc rõ ràng chia cấu trúc logic ra thành các thành phần riêng biệt của trách nhiện. Điều này làm dễ dàng tách riêng các thành phần và dễ dàng kiểm tra sự tương tác giữ các lớp.

2014-06-07-viper-intro-0a53d9f8.jpg

Như tôi đã nói ở trên thì phần lớn các ứng dụng iOS đều sử dụng mô hình MVC. Và với các dứ án lớn thì tôi đã nói nếu sử dụng MVC thì kết quả thế nào… Tại sao chúng ta không thử sử dụng mô hình mới được cho là tốt hơn? VIPER - cung cấp rõ ràng cho logic ứng dụng và các chuyển hướng liên quan. Khi bạn áp dụng mô hình này thì mã dễ hiểu, dễ kiểm tra và dễ bảo trì.

Các thành phần chính của VIPER

  • View: Hiển thị giao diện được điều khiển bởi Presneter, tiếp nhận tương tác với người dùng và truyền tới Presenter
  • Presenter: Nhận kết quả từ interactor và nhận user input hoặc request tới interactor
  • Interactor: Chức business logic tuỳ theo các case tương ứng
  • Entity: là các model object được interactor sử dụng
  • Routing: Điều hướng các màn hình

2014-06-07-viper-wireframe-76305b6d.png

Hiểu rõ về các thành phần trong mô hình VIPER

Thành phần Interactor

  • Một interactor đại diện cho một use case duy nhất trong ứng dụng. Nó thao tác với các thực thể Entity để thực hiện một nhiệm vụ cụ thể. Interactor được tách biệt độc lập với giao diện người dùng
  • Bạn có thể tham khảo đoạn code sau:
- (void)findUpcomingItems
{
    __weak typeof(self) welf = self;
    NSDate* today = [self.clock today];
    NSDate* endOfNextWeek = [[NSCalendar currentCalendar] dateForEndOfFollowingWeekWithDate:today];
    [self.dataManager todoItemsBetweenStartDate:today endDate:endOfNextWeek completionBlock:^(NSArray* todoItems) {
        [welf.output foundUpcomingItems:[welf upcomingItemsFromToDoItems:todoItems]];
    }];
}

Thành phần Entity

  • Là các model Object được điều khiển bởi một interactor
  • Ví dụ đoạn mã sau:
@interface VTDTodoItem : NSObject

@property (nonatomic, strong)   NSDate*     dueDate;
@property (nonatomic, copy)     NSString*   name;

+ (instancetype)todoItemWithDueDate:(NSDate*)dueDate name:(NSString*)name;

@end
  • Bạn đừng ngạc nhiên nếu thực thể của bạn chỉ là cấu trúc dữ liệu.

Thành phần Presenter

  • Chủ yếu gồm logic để điều khiển giao diện người dùng. Nó biết các trình bày giao diện người dùng. Nó tập hợp đầu vào từ tương tác người dùng để có thể cập nhậ nhanh giao diện người dùng và gửi yêu cầu tới tương tác.
  • Tiếp tục với ví dụ: khi người dùng chạm tay vào nút + để thêm mới. Đây được gọi là hành động từ người dùng. Presenter sẽ dẫn người dùng tới một mục (giao diện) mới.
- (void)addNewEntry
{
    [self.listWireframe presentAddInterface];
}

- (void)foundUpcomingItems:(NSArray*)upcomingItems
{
    if ([upcomingItems count] == 0)
    {
        [self.userInterface showNoContentMessage];
    }
    else
    {
        [self updateUserInterfaceWithUpcomingItems:upcomingItems];
    }
}
  • Các Entity không bao giờ được truyền từ Interactor tới Presenter

Thành phần View

  • Trong mô hình này nó chính là ViewController và các view control. Nhưng nó thụ động chờ tiếp nhận lệnh xử lý từ Presenter. Với mô hình MVC thì ViewController chứa rất nhiều logic xử lý giao diện. Thay vì vậy các công việc đó được Presenter và Interactor xử lý
@protocol VTDAddViewInterface <NSObject>

- (void)setEntryName:(NSString *)name;
- (void)setEntryDueDate:(NSDate *)date;

@end

Thành phần Routing

  • Chịu trách nhiệm định tuyến giữa các ViewController, Window, NavigationController @implementation VTDAddWireframe
- (void)presentAddInterfaceFromViewController:(UIViewController *)viewController
{
    VTDAddViewController *addViewController = [self addViewController];
    addViewController.eventHandler = self.addPresenter;
    addViewController.modalPresentationStyle = UIModalPresentationCustom;
    addViewController.transitioningDelegate = self;

    [viewController presentViewController:addViewController animated:YES completion:nil];

    self.presentedViewController = viewController;
}

#pragma mark - UIViewControllerTransitioningDelegate Methods

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [[VTDAddDismissalTransition alloc] init];
}

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source
{
    return [[VTDAddPresentationTransition alloc] init];
}

@end

Sử dụng VIPER để xây dựng modules

  • Thông thường khi làm việc với VIPER bạn sẽ thấy một màn hình hoặc thiết lập các màn hình có xu hướng đến nhau như một module
  • Có một lợi ích ở đây là viêc thiết kế ứng dụng là một tập hợp các module. Mỗi module là một giao diện rõ ràng và xác định, đôc lập với các module khác. Điều này giúp bạn có thể thêm hoặc gỡ bỏ các tính năng mới, cũ dễ dàng.
@protocol VTDAddModuleInterface <NSObject>

- (void)cancelAddAction;
- (void)saveAddActionWithName:(NSString *)name dueDate:(NSDate *)dueDate;

@end

@protocol VTDAddModuleDelegate <NSObject>

- (void)addModuleDidCancelAddAction;
- (void)addModuleDidSaveAddAction;

@end

Nguồn tham khảo và source code

All Rights Reserved