Upload
hendy-christianto
View
64
Download
4
Tags:
Embed Size (px)
Citation preview
Introduction to VIPER Architecture
Hendy Christianto
Why VIPER?• MVC - Massive View Controller
• What goes into View Controllers?
• Data sources for Views (UITableViews)
• Business Logic
• Application Flows
• Transition between view controller
What is VIPER?
• Based on clean architecture to Uncle Bob’s clean architecture.
• Basically a new architecture, introduced on 2014
• Used to resolve “Massive View Controller”
• Using “Single Responsibility” as a principle
Architecture of VIPER
View
• The View is passive
• Waits for the Presenter to give content to display
• Detect user interaction
• The view is an abstract interface
View Cont’d@protocol SignInEmailView <NSObject> - (void)setEmailLabelText:(NSString *)email; - (void)setPasswordLabelText:(NSString *)password; @end
#import <UIKit/UIKit.h> #import "SignInEmailView.h"
@interface ViewController : UIViewController <SignInEmailView>
@end
only protocols, abstract interface
implements SignInEmailView interface
View Presenter setPasswordLabelText:
Presenter
• Tells the view what to display
• Handle events
Presenter Cont’d@implementation ViewController #pragma mark - #pragma mark View Interface - (void)setEmailLabelText:(NSString *)email { self.emailLabel.text = email; }
- (void)setPasswordLabelText:(NSString *)password { self.passwordLabel.text = password; }
- (void)setErrorMessage:(NSString *)error { self.errorLabel.text = error; }
#pragma mark - #pragma mark IBAction - (IBAction)buttonLoginClicked:(id)sender { [self.presenter didLoginWithEmail:self.emailTextField.text password:self.passwordTextField.text]; }
Presenter View
buttonLoginClicked:
didLoginWithEmail: password:Interactor
signInWithEmail:password:
@implementation SignInPresenter
#pragma mark - #pragma mark Wireframe Update UI - (void)presentSignInEmailView { [self.view setPasswordLabelText:@"Password"]; [self.view setEmailLabelText:@"Email"]; }
- (void)didLoginWithEmail:(NSString *)email password:(NSString *)password { [self.interactor signInWithEmail:email password:password]; }
@end
#import <Foundation/Foundation.h> #import "SignInEmailView.h" #import "SignInInteractorIO.h"
@interface SignInPresenter : NSObject @property (nonatomic, weak) id <SignInEmailView> view; @property (nonatomic, strong) id <SignInInteractorInput> interactor;
- (void)presentSignInEmailView;
- (void)didLoginWithEmail:(NSString *)email password:(NSString *)password;
@end
Interactor
• Perform business logic
• Carry out events that notified by presenter (input)
• Produce output and notify back the presenter
Interactor Cont’d
Interactor Presenter signInWithEmail:password:
didSignInWithResponse:
@implementation SignInPresenter
#pragma mark - #pragma mark Wireframe Update UI - (void)presentSignInEmailView { [self.view setPasswordLabelText:@"Password"]; [self.view setEmailLabelText:@"Email"]; }
- (void)didLoginWithEmail:(NSString *)email password:(NSString *)password { [self.interactor signInWithEmail:email password:password]; }
#pragma mark - #pragma mark Interactor - (void)didSignInWithResponse:(NSDictionary *)response { NSError *error = response[@"error"]; if (error) { [self.view setErrorMessage:error.domain]; } else { [self.signInWireframe pushWelcomeView]; } }
@interface SignInInteractor ()
@property (nonatomic, strong) SignInDataManager *dataManager;
@end
@implementation SignInInteractor
- (void)signInWithEmail:(NSString *)email password:(NSString *)password { [self.dataManager userWithEmail:email password:password completion:^(User *user) { NSMutableDictionary *response = [NSMutableDictionary new]; if (user) { response[@"user"] = user; } else { response[@"error"] = [NSError errorWithDomain:@"User Not Found"
code:404 userInfo:nil]; } [self.output didSignInWithResponse:response]; }]; }
@end
@interface SignInInteractor : NSObject <SignInInteractorInput> @property (nonatomic, weak) id <SignInInteractorOutput> output; @end
@protocol SignInInteractorInput <NSObject>
- (void)signInWithEmail:(NSString *)email password:(NSString *)password;
@end
@protocol SignInInteractorOutput <NSObject>
- (void)didSignInWithResponse:(NSDictionary *)response;
Data Manager
userWithEmail:password:completion:
completion:^(User *user)
Data Manager
• Fetch data from database
• Restructure data to model / entities
• Store data
Data Manager Cont’d
Data Manager Interactor
@interface SignInInteractor ()
@property (nonatomic, strong) SignInDataManager *dataManager;
@end
@implementation SignInInteractor
- (void)signInWithEmail:(NSString *)email password:(NSString *)password { [self.dataManager userWithEmail:email password:password completion:^(User *user) { NSMutableDictionary *response = [NSMutableDictionary new]; if (user) { response[@"user"] = user; } else { response[@"error"] = [NSError errorWithDomain:@"User Not Found"
code:404 userInfo:nil]; } [self.output didSignInWithResponse:response]; }]; }
@end
Service
userWithEmail:password:completion:
@implementation SignInDataManager
- (void)userWithEmail:(NSString *)email password:(NSString *)password completion:(void (^)(User *))completionBlock { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(email == %@)", email]; [self.dataStore fetchEntriesWithPredicate:predicate sortDescriptors:@[] completionBlock:^(NSArray *results) { if (completionBlock) { if (results.count == 0) { completionBlock(nil); } else { completionBlock(results[0]); } } }]; }
@end
fetchEntriesWithPredicate:sortDescriptors:completionBlock
results arrayrun completionBlock:
Service
• Execute requests related to Entities / Models
• Network/ API, Database (local)
Entities
• Represent data
• Passed between class
Entities Cont’d
@interface User : NSManagedObject
@property (nonatomic, strong) NSString *email; @property (nonatomic, strong) NSString *name;
@end
@implementation User
@dynamic name; @dynamic email;
@end
Wireframe
• Initialize view controllers, Presenter, Interactor
• Handles routing / navigation within Views
Wireframe Cont’d@class SignInPresenter; @class WelcomeWireframe;
@interface SignInWireframe : RootWireframe
@property (nonatomic, strong) WelcomeWireframe *welcomeWireframe;
- (void)pushWelcomeView; - (void)presentSignInViewControllerOnWindow:(UIWindow *)window;
@end
static NSString *ViewControllerIdentifier = @"ViewController";
@interface SignInWireframe () @property (nonatomic, strong) SignInPresenter *presenter; @property (nonatomic, strong) SignInInteractor *interactor; @end
@implementation SignInWireframe
- (void)initializeClasses { self.presenter = [[SignInPresenter alloc] init]; self.interactor = [[SignInInteractor alloc] init]; self.presenter.interactor = self.interactor; self.interactor.output = self.presenter; }
- (void)presentSignInViewControllerOnWindow:(UIWindow *)window { [self initializeClasses]; ViewController *signInVC = [self signInViewController]; signInVC.presenter = self.presenter; self.presenter.view = signInVC; [self createNavigationControllerWithRootView:signInVC]; window.rootViewController = self.navigationController; }
- (void)pushWelcomeView { [self.welcomeWireframe pushWelcomeViewControllerOnNavigation:self.navigationController]; }
- (ViewController *)signInViewController { UIStoryboard *storyboard = [self mainStoryboard]; ViewController *signInVC = [storyboard instantiateViewControllerWithIdentifier:ViewControllerIdentifier]; return signInVC; }
Benefits of Viper
• Easy to iterate on
• Collaboration-friendly
• Separated concerns
• Easy to test
Conclusion
• Helps developer to be more explicit about separation of code
• Single responsibility each class, easier to maintain
• Neat Code!!
References• http://mutualmobile.github.io/blog/2013/12/04/viper-
introduction/
• http://www.objc.io/issue-13/viper.html
• https://medium.com/brigade-engineering/brigades-experience-using-an-mvc-alternative-36ef1601a41f
• http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
• iOS viper presentation - http://www.slideshare.net/RajatDatta1/i-os-viper-presentation
DEMOS
Q&A