«reactivecocoa и mvvm» — Николай Касьянов, softwear

30
REACTIVE COCOA & MVVM Николай Касьянов

Upload: e-legion

Post on 15-Jan-2015

1.295 views

Category:

Documents


5 download

DESCRIPTION

В докладе расскарывается тема использования функционально-реактивного подхода для разработки iOS- и Mac-приложений, его достоинства и недостатки. Также рассказано об использовании паттерна Model-View-View Model для улучшения архитектуры и повышения тестируемости GUI-кода.

TRANSCRIPT

Page 1: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

REACTIVE COCOA & MVVMНиколай Касьянов

Page 2: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

REACTIVE COCOA

• Objective-C framework for processing and composing streams

• Unifies async Cocoa patterns: callbacks, delegates, KVO, notifications

• Very composable

• Helps to minimize state

• Inspired by Reactive Extensions for .NET

Page 3: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

CALLBACK HELLvoid (^completion)(UIImage *) = ... id token = [self loadImage:url completion:^(NSData *data, NSError *error) { if (data == nil) { completion(defaultImage); } else { [self unpackImageFromData:data completion:^(UIImage *image) { if (image == nil) { // unpacking failed completion(defaultImage); } else { completion(image); } }] } }]; !// client code [imageLoader cancel:token]; !

Page 4: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

FUTURES

• Future can either complete with a value or reject with an error

• JavaScript Promises/A+

• There are some Objective-C implementations

• RAC can into futures too

Page 5: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal *image = [[[self rac_imageFromURL:url] flattenMap:^(NSData *data) { return [self rac_unpackedImageFromData:data]; }] catchTo:[RACSignal return:defaultImage]]; !// client code: RAC(cell.imageView, image) = [image takeUntil:cell.rac_prepareForReuseSignal];

Page 6: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal

• A stream of values

• One can subscribe to new value, error or completion

• Supports functional constructs: map, filter, flatMap, reduce etc

• Сold or hot

• A monad

Page 7: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal *allPosts = [RACSignal createSignal:^(id <RACSubscriber> s) { [httpClient GET:@"/posts.json" success:^(NSArray *posts) { [s sendNext:posts]; [s sendCompleted]; } failure:^(NSError *error) { [s sendError:error]; }]; ! return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; !RACSignal *posts = [[allPosts flattenMap:^(NSArray *items) { return items.rac_signal; }] filter:^(Post *post) { return post.hasComments; }]; // Nothing happened yet ![[posts collect] subscribeNext:^(NSArray *items) { NSLog(@"Posts: %@", items) }]; !RACDisposable *disposable = [posts subscribeCompleted:^{ NSLog(@"Done"); }]; // Ooops network request performed twice :( ![disposable dispose];

Page 8: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

STREAMS

• Powerful abstraction

• Streams for futures is like iterables for scalar values

• Umbrella concept for asynchronous Cocoa patterns

• You can even use signals as values. Yo dawg…

Page 9: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal *searchResults = [[[[textField.rac_textSignal throttle:1] filter:^(NSString *query) { return query.length > 2; }] map:^(NSString *query) { return [[[networkClient itemsMatchingQuery:query] doError:^(NSError *error) { [self displayError:error]; }] catchTo:[RACSignal empty]]; }] switchToLatest]; !// Automatically disposes subscription on self deallocation // cancelation running network request (if any) RAC(self, items) = searchResults;

Page 10: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

DEALING WITH IMPERATIVE API

• Property binding (one-way and two-way)

• Selector lifting (-rac_liftSelector:withSignals:)

• Signals from selector (-rac_signalForSelector:)

• Operators for injecting side-effects: doNext, doError, initially, finally

Page 11: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal *range = [[[RACSignal merge:@[ [self rac_signalForSelector:@selector(tableView:willDisplayCell...)] [self rac_signalForSelector:@selector(tableView:didEndDisplayingCell...)] ]] map:^(RACtuple *args) { UITableView *tableView = args[0]; NSArray *indexPaths = tableView.indexPathsForVisibleRows; NSRange range = NSMakeRange([indexPaths[0] row], indexPaths.count); return [NSValue valueWithRange:range]; }]; ![self rac_liftSelector:@selector(updateVisibleRange:) withSignals:range, nil];

Page 12: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

CONCURRENCY• RACScheduler

• -deliverOn: and -subscribeOn:- (RACSignal *)itemsFromDB { return [RACSignal createSignal:^(id <RACSubscriber> subscriber) { NSArray *items = [sqliteWrapper fetchItemsFromTable:@"posts"]; ! [subscriber sendNext:items]; [subscriber sendCompleted]; ! return nil; }]; } ![[[[self itemsFromDB] subscribeOn:[RACScheduler scheduler]] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSArray *items) { self.items = items; }];

Page 13: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

ISSUES• Steep learning curve

• Runtime overhead

• Tricky debugging: crazy call stacks, lots of asynchrony, retain cycles

• You’ll need to deal with imperative Cocoa API anyway

• Losing last bits of type information

Page 14: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

MVVM

Page 15: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

Model

View

View Controller

Page 16: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

MVVM

• Model – View – View Model

• An alternative to MVC

• MVC is fine, but…

• Meet UIViewController, The Spaghetti Monster

Page 17: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

Model View Model View

Page 18: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

MVVM

• MVVM knows nothing about a view

• MVVM uses underlying layers (persistence, web service clients, cache) to populate view data

• You can even use it with ncurses

Page 19: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

WHY MVVM?

• Clear separation between view and presentation logiс

• Reusability across different views and even platforms

• Testability

• View models are models

• Persistence can be hidden behind view model

Page 20: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

YOU ALREADY CLOSER TO MVVM THAN YOU THINK

Big monolithic view controllers

!

External data sources, data converters & services

!

MVVM

Page 21: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

@interface UserRepositoryViewModel : NSObject !// KVOable properties @property (nonatomic, copy, readonly) NSArray *items; @property (nonatomic, strong, readonly) User *selectedUser; @property (nonatomic, readonly) BOOL isLoading; !- (void)refreshItems; - (void)selectItemAtIndex; !@end

Page 22: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

MVVM + RAC

• KVO with RACObserve

• One-way binding: RAC(object, keyPath) = signal;

• Two-way binding: RACChannel

• RACCommand

Page 23: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

@interface LoginViewModel : NSObject !@property (nonatomic, copy) NSString *login; @property (nonatomic, copy) NSString *password; !@property (nonatomic, strong, readonly) RACCommand *login; !@end !!// Somewhere in view controller id viewTerminal = loginField.rac_newTextChannel; id modelTerminal = RACChannelTo(viewModel, login); ![[viewTerminal map:^(NSString *value) { return [value lowercaseString]; }] subscribe:modelTerminal]; [modelTerminal subscribe:viewTerminal]; !loginButton.rac_command = viewModel.login; [self rac_liftSelector:@selector(displayError:) withSignals:viewModel.login.errors, nil]; !RAC(spinner, animating) = viewModel.login.executing;

Page 24: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACCommand• Runs signal block on -execute: and subscribes to its result

• Multicasts execution signals to consumers

• Multicasts inner signal errors to consumers

• Enabled/disabled state can be controlled by a bool signal

• Exposes execution state (running/not running)

• Can be bound to UI control

Page 25: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RACSignal *networkReachable = RACObserve(httpClient, reachable); !RACCommand *login = [[RACCommand alloc] initWithEnabled:networkReachable signalBlock:^(id _) { // Boolean signal, sends @YES on success return [self.backendClient loginWithLogin:self.login password:self.password]; }]; !RAC(self, loggedIn) = [[login.executionSignals switchToLatest] startWith:@NO];

Page 26: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

• Make no assumptions about a view

• Expose bindable properties or signals

• Expose commands to consumers

• Throttle/unsubscribe signals when view is inactive

Page 27: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

ANY QUESTIONS?

Page 28: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

LINKS• https://github.com/ReactiveCocoa/ReactiveCocoa/

• https://github.com/ReactiveCocoa/ReactiveViewModel

• http://cocoamanifest.net/articles/2013/10/mvc-mvvm-frp-and-building-bridges.html

• https://rx.codeplex.com

• http://netflix.github.io/RxJava/javadoc/rx/Observable.html

Page 29: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

RAC-POWERED LIBRARIES

• https://github.com/octokit/octokit.objc

• https://github.com/jonsterling/ReactiveFormlets

• https://github.com/ReactiveCocoa/ReactiveCocoaLayout

Page 30: «ReactiveCocoa и MVVM» — Николай Касьянов, SoftWear

SAMPLE PROJECTS

• https://github.com/AshFurrow/C-41

• https://github.com/jspahrsummers/GroceryList/

• https://github.com/corristo/SoundCloudStream