Slide 1

Slide 1 text

ASYNC VIEWCONTROLLERS Tom Adriaenssen @inferis ∴ http://inferis.org

Slide 2

Slide 2 text

VIEW CONTROLLERS

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

ASYNC

Slide 5

Slide 5 text

ANATOMY OF ASYNC DATA 1. start slow call 2. show hey, keep yer pants on indicator 3. wait for call to finish 4. remove pants indicator 5. show your actual data

Slide 6

Slide 6 text

SOME HISTORY

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

MASSIVE VIEW CONTROLLER

Slide 11

Slide 11 text

MASSIVE VIEW CONTROLLER IPHONE - 1.0 // Lines of code, including whitespace * Hotspot: * HSViewController .h: 25, .m: 716 * HSBoxController .h: 44, .m: 370 * Portfolio: .h: 14, .m: 523

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

TECHNICAL DEBT

Slide 15

Slide 15 text

BREAK THINGS UP

Slide 16

Slide 16 text

INTRODUCING ASYNCVIEWCONTROLLER

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

@interface AsyncViewController : UIViewController @property (nonatomic, strong, readonly) UIView *asyncView; @property (nonatomic, strong, readonly) UIView *statusView; - (void)performAsyncDataRequest; @end

Slide 19

Slide 19 text

Y U NO SWIFT?

Slide 20

Slide 20 text

@property (nonatomic, strong) UIView *asyncView;

Slide 21

Slide 21 text

UIView *

Slide 22

Slide 22 text

@Inferis None yet. You can define a generic parameter , but that isn't representable as a type yet. — @jckarter

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

rdar://20990743

Slide 25

Slide 25 text

rdar://20990743

Slide 26

Slide 26 text

@interface AsyncViewController : UIViewController @property (nonatomic, strong, readonly) UIView *asyncView; @property (nonatomic, strong, readonly) UIView *statusView; - (void)performAsyncDataRequest; @end

Slide 27

Slide 27 text

SUBCLASS OF UIViewController

Slide 28

Slide 28 text

@property (nonatomic, strong, readonly) UIView *asyncView;

Slide 29

Slide 29 text

@property (nonatomic, strong, readonly) UIView *statusView;

Slide 30

Slide 30 text

- performAsyncDataRequest

Slide 31

Slide 31 text

ASYNC DATA FETCH & STATUSVIEW + ASYNCVIEW & VIEW CONTROLLER CONTAINMENT

Slide 32

Slide 32 text

SRP

Slide 33

Slide 33 text

A MORE PURE MVC

Slide 34

Slide 34 text

EASE OF USE

Slide 35

Slide 35 text

FLEXIBLE

Slide 36

Slide 36 text

TESTABLE

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

@protocol IIAsyncView @required @property (nonatomic, strong, readonly) id asyncData; - (void)asyncDataApplyValueAnimated:(BOOL)animated; @optional // ... @end

Slide 39

Slide 39 text

@protocol IIAsyncData @required @property (nonatomic, assign, readonly) BOOL loading; @property (nonatomic, strong) NSError *error; @property (nonatomic, strong) id value; - (void)reset; @end

Slide 40

Slide 40 text

typedef NS_ENUM(NSUInteger, AsyncStatusViewState) { IIAsyncStatusLoading, IIAsyncStatusError, IIAsyncStatusNoData, IIAsyncStatusData };

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

- (AsyncStatusViewState)determineState { // determine the new state if (![self isAsyncViewLoaded] || [self.asyncView.asyncData isLoading]) { return AsyncStatusLoading; } else if (self.asyncView.asyncData.error) { return AsyncStatusError; } else if (self.asyncView.asyncData.value) { return AsyncStatusData; } else { return AsyncStatusNoData; } }

Slide 43

Slide 43 text

@protocol IIAsyncData @required @property (nonatomic, assign, readonly) BOOL loading; @property (nonatomic, strong) NSError *error; @property (nonatomic, strong) id value; @property (nonatomic, weak) id asyncDataDelegate; - (void)reset; - (void)invalidateState; @end @protocol IIAsyncDataDelegate @required - (void)asyncData:(id)data didInvalidateStateForced:(BOOL)forced; - (void)reloadAsyncData; @end

Slide 44

Slide 44 text

@protocol AsyncView @required // ... @optional - (id)asyncNoDataMessage; - (BOOL)asyncCanReload; @end

Slide 45

Slide 45 text

@protocol AsyncStatusView @required @property (nonatomic, strong) UIView *asyncView; - (void)transitionToLoadingStateAnimated:(BOOL)animated; - (void)transitionToErrorState:(NSError*)error animated:(BOOL)animated; - (void)transitionToNoDataStateAnimated:(BOOL)animated; - (void)transitionToDataStateAnimated:(BOOL)animated; @end

Slide 46

Slide 46 text

@interface AsyncData : NSObject @interface AsyncView : UIView @interface AsyncStatusView : NSObject @interface AsyncViewController : UIViewController

Slide 47

Slide 47 text

@interface IIAsyncViewController : UIViewController - (void)reloadAsyncData; - (UIView*)loadStatusView; - (void)willTransitionToNewStateAnimated:(BOOL)animated; - (void)didTransitionToLoadingStateAnimated:(BOOL)animated; - (void)didTransitionToErrorStateAnimated:(BOOL)animated; - (void)didTransitionToDataStateAnimated:(BOOL)animated; - (void)didTransitionToNoDataStateAnimated:(BOOL)animated; @end

Slide 48

Slide 48 text

EXAMPLE

Slide 49

Slide 49 text

@interface SuchViewController () @property (nonatomic, strong) LaggyBackend *backend; @end @implementation SuchViewController - (void)performAsyncDataRequest { [self.backend thatReallySlowCall:^(id result) { self.asyncView.asyncData.value = result; } error:^(NSError* error) { self.asyncView.asyncData.error = error; }]; } @end

Slide 50

Slide 50 text

@interface SuchView () @property (nonatomic, strong) IBOutlet UILabel *label; @end @implementation SuchView - (void)asyncDataApplyValueAnimated:(BOOL)animated { self.label.text = [NSString stringWithFormat: @"Such %@", self.asyncData.value]; } - (BOOL)asyncCanReload { return self.asyncData.error != nil; }

Slide 51

Slide 51 text

UNDER THE HOOD

Slide 52

Slide 52 text

// In AsyncViewController - (void)viewDidLoad { [self wrapWithStatusView]; // call regular viewDidLoad [super viewDidLoad]; // force an update of the state to loading [self updateState:YES]; [self performAsyncDataRequest]; }

Slide 53

Slide 53 text

- (void)wrapWithStatusView { // don't wrap if there's a status view already if (self.statusView) return; // get async view. This is just self.view when first wrapping. UIView *asyncView = (UIView*)self.view; // now create the status view UIView *statusView = [self loadStatusView]; statusView.frame = asyncView.frame; statusView.asyncView = asyncView; asyncView.asyncData.asyncDataDelegate = self; // make main view the wrapping view [super setView:statusView]; }

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

@interface UIViewController (UILayoutSupport) // These objects may be used as layout items // in the NSLayoutConstraint API @property(nonatomic,readonly,strong) id topLayoutGuide; @property(nonatomic,readonly,strong) id bottomLayoutGuide; @end

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

1. find constraints related to layout guides before wrapping 2. wrap views 3. first layout pass: recreate constraints

Slide 58

Slide 58 text

- (NSArray*)findLayoutGuideConstraints:(UIView*)view { NSMutableArray *constraints = [NSMutableArray new]; UIViewController *viewController = [self findViewDelegate:view]; if (!viewController) return; for (NSLayoutConstraint *constraint in view.constraints) { if (constraint.class != [NSLayoutConstraint class]) continue; if (constraint.firstItem == viewController.topLayoutGuide || constraint.secondItem == viewController.topLayoutGuide) { [constraints addObject:@[constraint, @YES, viewController.topLayoutGuide]]; } else if (constraint.firstItem == delegate.bottomLayoutGuide || constraint.secondItem == delegate.bottomLayoutGuide) { [constraints addObject:@[constraint, @NO, viewController.bottomLayoutGuide]]; } } return [constraints copy]; }

Slide 59

Slide 59 text

- (UIViewController*)findViewDelegate:(UIView*)view

Slide 60

Slide 60 text

- (UIViewController*)findViewDelegate:(UIView*)view { const char *ivarName = [[NSString stringWithFormat:@"_%@Delegate", @"view"] UTF8String]; Ivar ivar = class_getInstanceVariable(view.class, ivarName); UIViewController *delegate = nil; if (ivar) { delegate = object_getIvar(view, ivar); if ([delegate isKindOfClass:[UIViewController class]]) { return delegate; } } return nil; }

Slide 61

Slide 61 text

! 

Slide 62

Slide 62 text

- (void)reapplyLayoutGuideConstraints:(NSArray*)constraintInfos { UIViewController *viewController = [self findViewDelegate:self]; if (!viewController) return; for (NSArray *constraintInfo in constraintInfos) { NSLayoutConstraint *oldConstraint = constraintInfo[0]; BOOL topGuide = [constraintInfo[1] boolValue]; // replace first and/or second item with new guides id firstItem = oldConstraint.firstItem == constraintInfo[2] ? (topGuide ? viewController.topLayoutGuide : viewController.bottomLayoutGuide) : : oldConstraint.firstItem; id secondItem = oldConstraint.secondItem == constraintInfo[2] ? (topGuide ? viewController.topLayoutGuide : viewController.bottomLayoutGuide) : oldConstraint.secondItem; // Create new constraint NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstItem attribute:oldConstraint.firstAttribute relatedBy:oldConstraint.relation toItem:secondItem attribute:oldConstraint.secondAttribute multiplier:oldConstraint.multiplier constant:oldConstraint.constant]; [self addConstraint:constraint]; } }

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

DETAILS

Slide 65

Slide 65 text

MASSIVE VIEW CONTROLLER A REPRISE

Slide 66

Slide 66 text

MASSIVE VIEW CONTROLLER IPHONE - 1.0 // Lines of code, including whitespace * Portfolio: .h: 14, .m: 523 * Hotspot: * HSViewController .h: 25, .m: 716 * HSBoxController .h: 44, .m: 370

Slide 67

Slide 67 text

MASSIVE VIEW CONTROLLER IPHONE - 1.2 // Lines of code, including whitespace * Portfolio: .h: 16, .m: 373 * Hotspot: * HSViewController: .h: 28, .m: 539 * HSBoxController: 41.h, .m: 108

Slide 68

Slide 68 text

MASSIVE VIEW CONTROLLER IPHONE - 1.0 VS 1.2 * Portfolio: .h: 114%, .m: 71% * Hotspot: * HSViewController: .h: 112%.h, .m: 75% * HSBoxController: .h: 93%.h, .m: 29%

Slide 69

Slide 69 text

¯\_()_/¯

Slide 70

Slide 70 text

TAKEAWAY

Slide 71

Slide 71 text

1. embrace View Controller Containment 2. reduce dependencies 3. avoid boilerplate 4. DRY 5. testable

Slide 72

Slide 72 text

OPEN SOURCE

Slide 73

Slide 73 text

Reference implementation: http://github.com/inferis/IIASyncViewController

Slide 74

Slide 74 text

THANKS! Get in touch! @inferis http://blog.inferis.org