Components Ari Grant Our Journey Layout of a feed story Code for - - PowerPoint PPT Presentation

components
SMART_READER_LITE
LIVE PREVIEW

Components Ari Grant Our Journey Layout of a feed story Code for - - PowerPoint PPT Presentation

Components Ari Grant Our Journey Layout of a feed story Code for a feed storys header Components Building blocks Data pipeline Optimizations NEWS FEED News Feed News Feed News Feed News Feed News Feed News Feed


slide-1
SLIDE 1

Ari Grant

Components

slide-2
SLIDE 2

Our Journey

  • Layout of a feed story
  • Code for a feed story’s header
  • Components
  • Building blocks
  • Data pipeline
  • Optimizations
slide-3
SLIDE 3

NEWS FEED

slide-4
SLIDE 4

News Feed

slide-5
SLIDE 5

News Feed

slide-6
SLIDE 6

News Feed

slide-7
SLIDE 7

News Feed

slide-8
SLIDE 8

News Feed

slide-9
SLIDE 9

News Feed

slide-10
SLIDE 10

News Feed

slide-11
SLIDE 11

News Feed

slide-12
SLIDE 12

News Feed

slide-13
SLIDE 13

News Feed

slide-14
SLIDE 14

News Feed

slide-15
SLIDE 15

A list of stories

News Feed

slide-16
SLIDE 16

iOS’ View APIs

Story Header Message Profile Picture Name Overlay

  • (CGSize)sizeThatFits:(CGSize)size

{ ... // Math }

  • (void)layoutSubviews

{ ... // Math }

Main (UI) Thread Only!

slide-17
SLIDE 17

On iOS

The Reality of a Complex UI

  • 60 fps is desired
  • Text layout can take many ms
  • Configuring many views is slow
  • Heterogeneous data
  • View sizing and layout can be slow
  • Views can only layout on the UI thread
  • Recycling code is manually written
  • You have to juggle text- and UI-threads
  • Views are mutable and always changing
  • Layout code is lots of math
  • 17 ms per frame
  • Use a background thread
  • Recycle views
  • Don’t use “row-based” view-types
  • Do sizing and layout in the background
  • Oh…
  • Oh…..
  • Oh…….
  • Oh………
  • Oh………..
slide-18
SLIDE 18

Goals

  • Easy to layout and recycle views
  • One-way data flow when state changes
  • Encourage composition and enforce immutability
  • Make the worst errors impossible
  • Allow asynchronous computation invisibly

Move complexity to “behind the scenes”

slide-19
SLIDE 19

BUILDING A FEED STORY

slide-20
SLIDE 20

Layout of a Feed Story

slide-21
SLIDE 21

Layout of a Feed Story

Vertical Stack

slide-22
SLIDE 22

Layout of a Feed Story

Vertical Stack

slide-23
SLIDE 23

Layout of a Feed Story

Text Vertical Stack

slide-24
SLIDE 24

Layout of a Feed Story

Text Horizontal Stack Vertical Stack

slide-25
SLIDE 25

Layout of a Feed Story

Image Text Horizontal Stack Vertical Stack

slide-26
SLIDE 26

Text

Layout of a Feed Story

Image Vertical Stack

Horizontal Stack Vertical Stack

slide-27
SLIDE 27

Text

Layout of a Feed Story

Image Vertical Stack Title Label Timestamp Label Horizontal Stack Vertical Stack

slide-28
SLIDE 28

Like Button Share Button

Layout of a Feed Story

Horizontal Stack Horizontal Stack Text Image Vertical Stack Vertical Stack Comment Button Title Label Timestamp Label

slide-29
SLIDE 29

Like Button Share Button

Layout of a Feed Story

Horizonal Stack Horizontal Stack Text Image Vertical Stack Comment Button

Vertical Stack Title Label Timestamp Label

slide-30
SLIDE 30

Layout of a Feed Story

Horizontal Stack Text Image Vertical Stack

Vertical Stack Title Label Timestamp Label Horizontal Stack Like Button Comment Button Share Button

slide-31
SLIDE 31

Layout of a Feed Story

Text

Vertical Stack

Horizontal Stack Like Button Comment Button Share Button Horizontal Stack Image Vertical Stack Title Label Timestamp Label

slide-32
SLIDE 32

Layout of a Feed Story

Vertical Stack Horizontal Stack Like Button Comment Button Share Button Horizontal Stack Image Vertical Stack Title Label Timestamp Label Text

slide-33
SLIDE 33

Feed Story Header

<Stack direction=“horizontal” spacing=10> <Image contents={image} /> <Stack direction=“vertical” spacing=12> <Label text={title} /> <Label text={timestamp} /> </Stack> </Stack>

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

slide-34
SLIDE 34

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ ... } children:{ ... }]

slide-35
SLIDE 35

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ ... } children:{ ... }] children:@[ ... ]

?

children:{ {[Foo new], .bar = 7}, {[Baz new]} }

children:@[ [StackLayoutChild newWithComponent:[Foo new] bar:7], [StackLayoutChild newWithComponent:[Baz new]] ]

slide-36
SLIDE 36

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ ... } children:{ ... }]

slide-37
SLIDE 37

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ ... }]

slide-38
SLIDE 38

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, ... }]

slide-39
SLIDE 39

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ ... } children:{ ... }]} }]

slide-40
SLIDE 40

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ ... }]} }]

slide-41
SLIDE 41

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, ... }]} }]

slide-42
SLIDE 42

Feed Story Header

Horizontal Stack Image Vertical Stack Title Label Timestamp Label

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, {[CPLabelComponent newWithText:timestamp]} }]} }]

slide-43
SLIDE 43

Feed Story Header

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, {[CPLabelComponent newWithText:timestamp]} }]} }]

slide-44
SLIDE 44

Feed Story Header

[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, {[CPLabelComponent newWithText:timestamp]} }]} }]

slide-45
SLIDE 45

Feed Story Header

return [super newWithComponent: [CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, {[CPLabelComponent newWithText:timestamp]} }]} }] ];

slide-46
SLIDE 46

Feed Story Header

+ (instancetype)newWithImage:(UIImage *)image title:(NSString *)string timestamp:(NSString *)timestamp { return [super newWithComponent: [CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionHorizontal, .spacing = 10 } children:{ {[CPImageComponent newWithImage:image]}, {[CPStackLayoutComponent newWithStyle:{ .direction = CPStackLayoutDirectionVertical, .spacing = 12 } children:{ {[CPLabelComponent newWithText:title]}, {[CPLabelComponent newWithText:timestamp]} }]} }] ]; }

slide-47
SLIDE 47

Feed Story Header

+ (instancetype)newWithImage:(UIImage *)image title:(NSString *)string timestamp:(NSString *)timestamp { CPComponent *component = storyHeader(image, title, timestamp); return [super newWithComponent:component]; } static CPComponent *storyHeader(UIImage *image, NSString *title, NSString *timestamp) { return ...; }

slide-48
SLIDE 48

Feed Story Header

@interface StoryHeaderComponent : CPCompositeComponent + (instancetype)newWithImage:(UIImage *)image title:(NSString *)string timestamp:(NSString *)timestamp; @end @implementation StoryHeaderComponent + (instancetype)newWithImage:(UIImage *)image title:(NSString *)string timestamp:(NSString *)timestamp { CPComponent *component = storyHeader(image, title, timestamp); return [super newWithComponent:component]; } @end

slide-49
SLIDE 49

COMPONENTS

slide-50
SLIDE 50

Wrapping a View

[CPComponent newWithView:{ [UIView class], { { @selector(setBackgroundColor:), [UIColor orangeColor] }, { @selector(setAlpha:), @0.5 }, } } size:{} ] [CPComponent newWithView:{ [UIImageView class], { { @selector(setImage:), [UIImage imageNamed:@“toaster.png”] }, } } size:{} ]

slide-51
SLIDE 51

Sizing A Component

[CPComponent newWithView:{ [UIView class], { { @selector(setBackgroundColor:), [UIColor orangeColor] }, { @selector(setAlpha:), @0.5 }, } } size:{ 100, 200 } ] { 100, Percent(50) } { Percent(100), 200 } { Percent(100), Percent(50) } { Auto(), Auto() }

“Autonomous”

{} { width, height } { .minWidth = 50, .maxWidth = 250, .minHeight = 0, .maxHeight = Percent(100), }

slide-52
SLIDE 52

The Root Class

CPComponent

  • Configure a(n optional) view
  • Can take an “explicit” size
  • Privately produces the sized view
  • Immutable
  • Everything is configured in +newWith…
  • Components are like pure functions
  • Internal methods for layout, view configuration, and receiving events
slide-53
SLIDE 53

A Class Built for Composition

CPCompositeComponent

  • Wraps one component via +newWithComponent:
  • Forwards sizing and configuration

@interface StoryHeaderComponent : CPCompositeComponent + (instancetype)newWithImage:(UIImage *)image title:(NSString *)string; @end @implementation StoryHeaderComponent + (instancetype)newWithImage:(UIImage *)image title:(NSString *)string { return [super newWithComponent:storyHeader(image, title)]; } @end

slide-54
SLIDE 54

Subclass CPCompositeComponent and wrap a “primitive”

Building Blocks

CPInsetComponent

CPStackLayoutComponent CPOverlayComponent CPStaticLayoutComponent CPBackgroundComponent CPTextComponent CPImageComponent CPButtonComponent CPScrollComponent

slide-55
SLIDE 55

MATH

slide-56
SLIDE 56

Flexbox

slide-57
SLIDE 57

Flexbox

slide-58
SLIDE 58

Creating Primitives

  • Subclass CPCompositeComponent
  • Wrap existing components
  • Wrap custom UIViews
  • Subclass CPComponent
  • Layout: implement -layoutThatFits:
  • Configuration: override -mountInContext:

Rarer than you think

slide-59
SLIDE 59

DATA FLOW

slide-60
SLIDE 60

MVC

slide-61
SLIDE 61

Image Credit: Apple

User action Update Notify Update

Controller View Model

MVC: Model-View-Controller

slide-62
SLIDE 62

MVC - Views

Feed View Story View Like View Story View Like View Story View

slide-63
SLIDE 63

Model Store Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-64
SLIDE 64

Model Store Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-65
SLIDE 65

Model Store Feed View Story View Like View Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-66
SLIDE 66

Model Store Feed View Story View Like View Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-67
SLIDE 67

Model Store Feed View Story View Like View Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-68
SLIDE 68

Model Store Feed View Story View Like View Feed Controller Story Controller Like Controller

MVC Data and Event Flow

slide-69
SLIDE 69
slide-70
SLIDE 70

MUTABILITY

slide-71
SLIDE 71

Immutable

  • (void)userTappedLike

{ [FBAPI sendLikeRequestForStory:self.story]; self.story.doesLike = YES; self.story.likeCount += 1; self.likeButton.selected = YES; [self.likeButton addAnimation:FBLikeAnimation()]; self.likeCountLabel.text = [self likeCountText]; if (self.likeCountLabel.hidden) { self.likeCountLabel.hidden = NO; [self.view setNeedsLayout]; } }

  • (void)userTappedLike

{ [FBMutator applyLikeMutation:self.story]; }

slide-72
SLIDE 72

Feed Component Story Component Like Component

does_like = true

Feed Component Story Component Like Component

Components Data and Event Flow

Model Store

slide-73
SLIDE 73

Infra

Story Mem Model

Developer

Story Component Story View Hierarchy

Story Header Message Profile Picture Name

Story Model

Card Image Text Box Image Label

Rendering Pipeline

slide-74
SLIDE 74
slide-75
SLIDE 75

CPU MUCH?

slide-76
SLIDE 76

Keeping the CPU under control

Optimizations

  • Allocate and size components ofg the UI thread
  • Recycle views
  • Stack-allocate objects
  • Minimize configuration by difgerentiating attributes that:
  • can be smashed over
  • need to be “unconfigured”
  • have a shortcut if the attribute has already been applied
slide-77
SLIDE 77

View Recycling

Card Image Text Box Image Label

Label at the top.

Image

The best text-box the world has ever seen. Totes.

Card Image Video Image Label

Label at the top.

Image

The best text-box the world has ever seen. Totes. Label at the top.

Image

The best text-box the world has ever seen. Totes.

Card Image Image

Image

Label

Wicked cool label.

Video

slide-78
SLIDE 78

INTERNAL STATE

slide-79
SLIDE 79

Immutability and Encapsulation

  • Components are immutable (short-lived)
  • Components cannot have mutable state
  • New state can be handed to new components
  • Feels just like they are long lived
slide-80
SLIDE 80

Component State

+ (instancetype)newWithColor:(UIIColor *)color { NSNumber *alpha = self.state(^id { return @1.0; }); return [super newWithComponent: [FBComponent newWithView:{[UIView class], { { @selector(setBackgroundColor:), color }, { @selector(setAlpha:), alpha}, }} size:{}]]; }

  • (void)handleDimEvent:(id)sender

{ [self updateState:^id(NSNumber *currentValue) { return @([currentValue floatValue] * 0.8); }]; }

slide-81
SLIDE 81

PUTTING IT ALL TOGETHER

slide-82
SLIDE 82

Don’t make me…

  • Configure views
  • Implement view recycling
  • Write math for layout and sizing
  • Listen for state changes
  • Worry about threads and mutability
slide-83
SLIDE 83

Do the hard work for me

  • “This is the view hierarchy I want.”
  • The system recycles and configures views.
  • “This is the layout I want.”
  • The system does all the math.
  • “This is the state-change I want.”
  • The system runs the component creation function and provides the new state.
  • Declare all the things.
slide-84
SLIDE 84

BETTER 


PROGRAMMING MODEL

slide-85
SLIDE 85

70% LESS CODE 


TO WRITE NEWS FEED

slide-86
SLIDE 86

PERFORMANT


OUT OF THE BOX

slide-87
SLIDE 87

Some of the News Feed Story Component

[CPStackLayoutComponent newWithView:{} style:{ .direction = CPStackLayoutDirectionVertical, .alignItems = CPStackLayoutAlignItemsStretch, .spacing = 10, } children:{ {[CPStoryHeaderComponent newWithStory:story]}, {[CPStoryMessageComponent newWithStory:story]}, {[CPStoryLikeCommentComponent newWithStory:story]} }]

slide-88
SLIDE 88

THE END

slide-89
SLIDE 89

QUESTIONS?