#mbltdev: Опыт использования mvvm в реальных проектах

48
MVVM в реальных проектах Юрий Буянов Одноклассники

Upload: e-legion

Post on 26-Jun-2015

2.357 views

Category:

Mobile


1 download

DESCRIPTION

#MBLTdev: Конференция мобильных разработчиков Спикер: Юрий Буянов Разработчик мобильных приложений, Одноклассники http://mbltdev.ru/

TRANSCRIPT

Page 1: #MBLTdev: Опыт использования MVVM в реальных проектах

MVVM в реальных проектах

Юрий Буянов “Одноклассники”

Page 2: #MBLTdev: Опыт использования MVVM в реальных проектах

MVVM

Page 3: #MBLTdev: Опыт использования MVVM в реальных проектах

MVC

Page 4: #MBLTdev: Опыт использования MVVM в реальных проектах

“Massive View Controller”

• Занимается вообще всем

• Сильная связанность с View layer

• Трудно тестировать

• Трудно переиспользовать

Page 5: #MBLTdev: Опыт использования MVVM в реальных проектах

Model-View-ViewModel

ViewModel

Model

View(+ViewController) Lightweight View

UI logic

Business logic

Page 6: #MBLTdev: Опыт использования MVVM в реальных проектах

Model-View-ViewModel

ViewModel

Model

View(+ViewController) Lightweight View

UI logic

Business logic

Bindings!

Page 7: #MBLTdev: Опыт использования MVVM в реальных проектах

ViewModel• Не знает ничего про ViewController

• Не использует UIKit

• Отображает (?) состояние UI с помощью KVO-совместимых свойств или RAC-сигналов

• Действия представлены в виде методов или RAC-команд

• Легко тестируема (и должна тестироваться)

Page 8: #MBLTdev: Опыт использования MVVM в реальных проектах

View (ViewController)

• Декларативно привязывает состояние UI к состоянию VM

• Обрабатывает чистую логику UI (вёрстка, анимации, ориентирование)

• Может зависеть от нескольких ViewModel

Page 9: #MBLTdev: Опыт использования MVVM в реальных проектах

Биндинги

- (void)viewDidLoad { [super viewDidLoad];

RAC(self.trackTitleLabel, text) = RACObserve(self.viewModel, trackTitle);

RAC(self.playBtn, enabled) = RACObserve(self.viewModel, loading).not; }

- (IBAction)nextBtnTap:(id)sender { [self.viewModel nextBtnPressed]; }

Page 10: #MBLTdev: Опыт использования MVVM в реальных проектах
Page 11: #MBLTdev: Опыт использования MVVM в реальных проектах

View

UI customization layout

animations orientation changes

ViewModel

Model interaction (network, storage)

Model-related UI state

???

Navigation Image Loading

Action Confirmations UI-only actions

Page 12: #MBLTdev: Опыт использования MVVM в реальных проектах

Навигация

Page 13: #MBLTdev: Опыт использования MVVM в реальных проектах
Page 14: #MBLTdev: Опыт использования MVVM в реальных проектах

Ad hocUINavigationController

Sections Controller

Talks Controller

ctrl = [[TalksController alloc] init]

push2

1 инициализация

Page 15: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsController

- (IBAction)mobileBtnTap:(id)sender { UIViewController *ctrl = [[TalksController alloc] init]; [self.navigationController pushViewController:ctrl animated:YES]; }

@end

Page 16: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsController

- (IBAction)mobileBtnTap:(id)sender { UIViewController *ctrl = [[TalksController alloc] init]; [self.navigationController pushViewController:ctrl animated:YES]; }

@end

Ненужная связанность (coupling)

Page 17: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller Talks Controller

push4

2

создаёт

Sections ViewModel

Talks ViewModel

действие1

Ad Hoc + MVVM

создаёт

3

Page 18: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsViewModel

- (void)mobileBtnPressed { MobileTalksViewModel *vm = [[MobileTalksViewModel alloc] init];

UIViewController *ctrl = [[TalksController alloc] initWithVM:vm];

[self.navigationController pushViewController:ctrl animated:YES]; }

@end

Page 19: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsViewModel

- (void)mobileBtnPressed { MobileTalksViewModel *vm = [[MobileTalksViewModel alloc] init];

UIViewController *ctrl = [[TalksController alloc] initWithVM:vm];

[self.navigationController pushViewController:ctrl animated:YES]; }

@end

Ненужная связанность

UI-код внутри ViewModel! :(

Page 20: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller

2 создаётSections ViewModel

Talks ViewModel

действие1

Решение “В лоб”

Page 21: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller

2 создаётSections ViewModel

Talks ViewModel

действие1

Решение “В лоб”

3 сигнал

Page 22: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller Talks Controller

2 создаётSections ViewModel

Talks ViewModel

действие1

Решение “В лоб”

3 сигнал

4 создаёт и связывает

Page 23: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller Talks Controller

push5

2 создаётSections ViewModel

Talks ViewModel

действие1

Решение “В лоб”

3 сигнал

4 создаёт и связывает

Page 24: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsViewModel

- (void)mobileBtnPressed { MobileTalksViewModel *vm = [[MobileTalksViewModel alloc] init]; [_talksViewModelsSubject sendNext:vm]; }

@end

Page 25: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsController

- (IBAction)mobileBtnTap:(id)sender { [self.viewModel mobileBtnPressed]; }

- (void)viewDidLoad {

[self.viewModel.talksViewModelsSignal subscribeNext:^(id viewModel) {

UIViewController* ctrl = [[TalksController alloc] initWithVM:viewModel]; [self.navigationController pushViewController:ctrl animated:YES];

}]; }

@end

Page 26: #MBLTdev: Опыт использования MVVM в реальных проектах

Решение “В лоб”

• Большая степень связанности в VM и контроллере

• Негибкость

• Повторение кода

• Отдельный сигнал для каждого “маршрута”

Page 27: #MBLTdev: Опыт использования MVVM в реальных проектах

Роутер (Navigation Object)

Page 28: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller

Sections ViewModel

action showTalks1 2

MVVM + Роутер

Page 29: #MBLTdev: Опыт использования MVVM в реальных проектах

UINavigationController

Sections Controller

showDst

MVC + Роутер

Page 30: #MBLTdev: Опыт использования MVVM в реальных проектах

Роутер

• Имеет доступ к навигационным контроллерам (navigation controller, tab controller, drawer controller, menu controller)

• Создаёт и конфигурирует экземпляры VM и контроллеров

• (сам или используя DI-контейнер)

• Осуществляет переходы между экранами

• Mockable

• Отлично работает в рамках MVVM и MVC

Page 31: #MBLTdev: Опыт использования MVVM в реальных проектах

Роутер@interface Router : NSObject

- (void)showMobileTalks;

- (void)showWebTalks;

- (void)showGamesTalks;

- (void)showTalk:(Talk*)talk;

@end

Page 32: #MBLTdev: Опыт использования MVVM в реальных проектах

Роутер

@implementation Router

- (void)showMobileTalks { MobileTalksViewModel *vm = [[MobileTalksViewModel alloc] init]; UIViewController *ctrl = [[TalksController alloc] initWithVM:vm]; [self.mainNavigationController pushViewController:ctrl animated:YES]; }

@end

Page 33: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsController

- (IBAction)mobileBtnTap:(id)sender { [self.viewModel mobileBtnPressed]; }

- (void)viewDidLoad {

[self.viewModel.talksViewModelsSignal subscribeNext:^(id viewModel) {

UIViewController* ctrl = [[TalksController alloc] initWithVM:viewModel]; [self.navigationController pushViewController:ctrl animated:YES];

}]; }

@end

Page 34: #MBLTdev: Опыт использования MVVM в реальных проектах

@implementation SectionsViewModel

- (void)mobileBtnPressed { [self.router showMobileTalks]; }

@end

Page 35: #MBLTdev: Опыт использования MVVM в реальных проектах

Вложенные контроллеры

UINavigationController

Controller

Child controller

Some other controller

ViewModel

Child ViewModel

Some other ViewModel

Page 36: #MBLTdev: Опыт использования MVVM в реальных проектах

Вложенные контроллеры

UINavigationController

Controller

Child controller

Some other controller

ViewModel

Child ViewModel

Some other ViewModel

Page 37: #MBLTdev: Опыт использования MVVM в реальных проектах

Универсальные приложения

Page 38: #MBLTdev: Опыт использования MVVM в реальных проектах

[self.router showMobileTalks];

[self.router open:@"/sections/mobile" modal:NO];

Частный случай: URL-based роутер

Page 39: #MBLTdev: Опыт использования MVVM в реальных проектах

URL-based роутер• Легко настраивается

• Упрощает поддержку URL-схем

• Упрощает переход от веб-приложений к нативным

• Логика навигации может “утекать” в VM/контроллер

• Сложности с передачей сложных объектов

• Подходит не для всех приложений

Page 40: #MBLTdev: Опыт использования MVVM в реальных проектах

Списки (UICollectionView, UITableView)

Page 41: #MBLTdev: Опыт использования MVVM в реальных проектах

Модель ячейкиViewController ViewModel

- (void)setViewModel:(MYCellViewModel *)viewModel

UICollectionView/UITableView

Cell

CellViewModel

CellViewModel

CellViewModel

CellViewModel

- (void)viewDidLoad { [super viewDidLoad]; [self.viewModel.updatedContentSignal subscribeNext:^(id x) { [self.collectionView reloadData]; }]; }

Cell

Cell

@property RACSubject *updatedContentSignal;

NSArray *cellViewModels

Page 42: #MBLTdev: Опыт использования MVVM в реальных проектах

Статическая модель ячейки

Cell

Cell ViewModel

- (void)setViewModel:(OKPTrackCellViewModel *)viewModel { NSParameterAssert(viewModel); self.titleLabel.text = viewModel.title; self.subTitleLabel.text = viewModel.subtitle; self.someIcon.hidden = !viewModel.someFlag; }

@property NSString* title;

@property NSString* subtitle;

@property BOOL someFlag;

Page 43: #MBLTdev: Опыт использования MVVM в реальных проектах

Динамическая модель ячейки

Cell

Cell ViewModel

- (void)setViewModel:(OKPTrackCellViewModel *)viewModel { NSParameterAssert(viewModel); self.titleLabel.text = viewModel.title; self.subTitleLabel.text = viewModel.subtitle; self.someIcon.hidden = !viewModel.someFlag; }

@property NSString* title;

@property NSString* subtitle;

@property BOOL someFlag; //Может изменяться!

Page 44: #MBLTdev: Опыт использования MVVM в реальных проектах

Динамическая модель ячейки

Cell

RAC(self.someIcon, hidden) = RACObserve(viewModel.someFlag).not;

Page 45: #MBLTdev: Опыт использования MVVM в реальных проектах

Warning: cell reuseCell

RAC(self.someIcon, hidden) = RACObserve(viewModel.someFlag).not;

/// A given key on an object should only have one active signal bound to it at any given time. Binding more than one signal to the same property is considered undefined behavior.

Page 46: #MBLTdev: Опыт использования MVVM в реальных проектах

Warning: cell reuseCell

RAC(self.someIcon, hidden) =[RACObserve(viewModel.someFlag).not takeUntil:self.rac_prepareForReuseSignal];

/// Solution: unsubscribe from RACObserve signal on cell reuse

Page 47: #MBLTdev: Опыт использования MVVM в реальных проектах

Ощущения (на данном этапе)

• Технология “на вырост”

• Сделает возможным написание тестов, но не напишет их за вас

• Хинт: используйте Dependency Injection.

• Вам придётся полюбить ReactiveCocoa

Page 48: #MBLTdev: Опыт использования MVVM в реальных проектах

Спасибо