"Architecting and testing large iOS apps: lessons from Facebook". Adam Ernst, Facebook

Preview:

DESCRIPTION

In 2012 Facebook relaunched their iOS app to use native code. This was a big shift in architecting and implementing the Facebook app experience, the most widely used third party app on the entire iOS platform. Adam Ernst will speak about how the decision was made to switch to native code and how the company prepared to rewrite the app. He will share an inside look at the APIs and technical architecture Facebook uses to enable dozens of iOS developers to work on the same application. Automated testing is very important to Facebook, so Adam will also speak about how Facebook uses testing on iOS to keep the app reliable.

Citation preview

Adam Ernst

Architecting and Testing Large iOS Apps: Lessons from Facebook

Facebook

developers

Adam Ernstadamjernst@fb.com

@adamjernstgithub.com/adamjernst

Testing Facebook for iOS

Agenda

▪ “Over the wall”: some history

▪Product Teams and the Core Team

▪Scheduling and Stabilizing Releases

▪How We Develop

▪Eating the Dogfood: Builds

▪A Culture of Unit Testing

▪Future of iOS at Facebook

History

Facebook for Mobile

▪Web deeply engrained in Facebook’s DNA

▪Use HTML!/Javascript within “wrapper” native app

▪Developed our own framework for advanced integration(image uploads, photo browsers, mixing native/web elements)

HTML as an app platform

▪What does it bring us?

One CodebaseInstant Updates A/B Testing

HTML as an app platform

A few engineers in a room

▪Facebook for iOS !." began as an experiment

A few engineers in a room

▪Facebook for iOS !." began as an experiment

▪Could we achieve better results with native code?

A few engineers in a room

▪Facebook for iOS !." began as an experiment

▪Could we achieve better results with native code?

▪High barrier: requires rewriting every part of Facebook’s mobile UI!

A few engineers in a room

▪Facebook for iOS !." began as an experiment

▪Could we achieve better results with native code?

▪High barrier: requires rewriting every part of Facebook’s mobile UI!

▪Couldn’t disrupt company for something that might not ship(At the time, few iOS engineers at Facebook anyway)

A few engineers in a room

▪Facebook for iOS !." began as an experiment

▪Could we achieve better results with native code?

▪High barrier: requires rewriting every part of Facebook’s mobile UI!

▪Couldn’t disrupt company for something that might not ship(At the time, few iOS engineers at Facebook anyway)

▪Sequester some engineers in a war room, and have themwrite the product from top to bottom

Reaction

▪Press loved it

▪More importantly…

▪ Perceived Speed way up

▪ User Ratings way up

▪#x Speed increase!

Reaction

▪Press loved it

▪More importantly…

▪ Perceived Speed way up

▪ User Ratings way up

▪#x Speed increase!

Team

A multi-app ecosystem

A multi-app ecosystem

The Team Sandwich

Release Team: Release & Stabilization ProcessRelease Team: Release & Stabilization ProcessRelease Team: Release & Stabilization ProcessRelease Team: Release & Stabilization ProcessRelease Team: Release & Stabilization Process

Photos Feed Search Places Messages

Product Infrastructure Team: Shared LibrariesProduct Infrastructure Team: Shared LibrariesProduct Infrastructure Team: Shared LibrariesProduct Infrastructure Team: Shared LibrariesProduct Infrastructure Team: Shared Libraries

Product Cycle

Fixed Release Cycle

▪Waiting for all features in a release to be “done” slowed us down, and we want to move fast

▪So, ship an update every X weeks

▪ … no matter what

▪Popularized by Mozilla

Building features with an “off switch”

▪Every feature must be built with a way to turn it off

▪ If a feature destabilizes the build or isn’t complete, turn it off and try again next time

▪#defines – or runtime switches (preferred)

Building a new “Like Bar”

Building a new “Like Bar”

Development

Phabricator

Text

Phabricator

Text

Phabricator

Text

Phabricator

Text

Phabricator

Text

Check code

▪ ‘arc lint’

▪Set up rules to catch common mistakes

▪Examples:

▪ Enforce style guidelines

▪ Warn against using certain symbols

▪ Check for common pattern mistakes, and fix them!

@implementation SampleClass

- (void)setupAccessibility{ self.myView.accessibilityTraits = UIAccessibilityTraitHeader;}

- (void)dealloc{ [_myString dealloc]; [super dealloc];}

@end

RegEx Lint Rule

AST Lint Rule

NEW

clang

AST

AST Lint Rules

▪Regular expressions have false positives and negatives

▪ Dot notation vs braces

▪ Even mentioning forbidden API in a comment triggers rule!

▪AST lint rules can “understand the code”

Verify changes: Buildbot

▪Continuous integration

▪Distributed across multiple build machines

▪Sanity check:

▪ Do all projects still build?

▪ Do all unit tests still pass?

▪Emails engineer, and updates Phabricator on failure

Static Analyzer Remote Runs

Buildbot failure email

Builds

Multiple Builds

▪Use different bundle IDs and icons for different types of builds:

▪ Development

▪ Daily Build

▪ App Store release

▪ Special branches

▪Burn your build number into your icon

Branching

▪Two concurrent branches:

▪Master

▪ Engineers make progress on future features

▪ All changes checked in here first

▪ …including bug fixes for Releases

▪Release

▪ Once verified in Master…

▪ …“Release team” pulls them into Release branch

Testing

Testing is important to Facebook

▪Not in Facebook’s culture:

▪ SDEs “in Test”

▪ Large QA departments

▪Definitely in Facebook culture:

▪ High quality, reliable user experience

▪We believe in developer-authored unit tests

Snapshot Unit Tests

typedef NS_ENUM(NSUInteger, FBActionStyle) { FBActionStyleUndefined = 0, FBActionStyleDefault = 1, FBActionStyleProminent = 2, FBActionStyleSubdued = 3,}

static NSArray *FBActionGetStyles() { return @[@"DEFAULT", @"PROMINENT", @"SUBDUED"];}

NSString *FBActionStringFromStyleValue(FBActionStyle styleValue) { NSArray *styles = FBActionGetStyles();

if (styleValue < [styles count]) { return [styles objectAtIndex:styleValue]; } else { return nil; }}

typedef NS_ENUM(NSUInteger, FBActionStyle) { FBActionStyleUndefined = 0, FBActionStyleDefault = 1, FBActionStyleProminent = 2, FBActionStyleSubdued = 3,}

NSString *FBActionStringFromStyleValue(FBActionStyle styleValue) { switch (styleValue) { case FBActionStyleDefault: return @”DEFAULT”; case FBActionStyleProminent: return @”PROMINENT”; case FBActionStyleSubdued: return @”SUBDUED”; } return nil;}

Old Code: New Code:

Snapshot Test Case Failures

2013-09-24 17:59:01.743 FBAppKitTestHost[44975:a0b] If you have Kaleidoscope installed you can run this command to see an image diff:ksdiff "/Users/adamjernst/Library/Application Support/iPhone Simulator/7.0/Applications/D85DEF94-79B2-49AB-ABAA-093044D754CF/tmp/FBMegaphoneViewSnapshotTests/reference_testOverflowContent_socialContext_pad@2x.png" "/Users/adamjernst/Library/Application Support/iPhone Simulator/7.0/Applications/D85DEF94-79B2-49AB-ABAA-093044D754CF/tmp/FBMegaphoneViewSnapshotTests/failed_testOverflowContent_socialContext_pad@2x.png"/Users/adamjernst/Documents/fbobjc/Libraries/FBAppKit/FBAppKitTests/FBMegaphoneViewSnapshotTests.m:276: SenTestFailureException: "comparisonSuccess__" should be true. Snapshot comparison failed: Error Domain=FBTestSnapshotControllerErrorDomain Code=4 "Images different" UserInfo=0x7b660230 {NSLocalizedDescription=Images different}:

273 FBMegaphoneStoryView *view = [FBMegaphoneStoryView viewForMegaphone:megaphone target:nil frame:CGRectMake(0, 0, 768, 320)];274 NSString *newIdentifier = identifier.length ? [NSString stringWithFormat:@"%@_pad", identifier] : @"pad";275 [view sizeToFit];276 FBSnapshotVerifyView(view, newIdentifier); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

github.com/facebook/ios-snapshot-test-case

NEW

Watchdog Timer

Main Thread work work work work workwork

Watchdog Timer

Main Thread work work work work workwork

Watchdog Thread

ping ac

k

ping ac

k

ping ac

k

ping ac

k

ping ac

k

ping

Uh oh! No ack.Log backtrace.

Watchdog Timer: How it Works

▪Max-priority thread pings main thread every X seconds

▪ If main thread doesn’t respond in time…

▪ Freeze the main thread

▪ Get the backtrace to see what code is running

▪ Log it to our servers for analysis

github.com/facebook/ios-watchdog-timer

NEW

Rage Shake

▪Shake the device to report a bug

▪Captures:

▪ Screen shot (with annotations!)

▪ Network logs

▪ Last crash log

▪ Last x-seconds of logging

▪ Dumps the view hierarchy

▪Automatically files a bug and sends it to Facebook

Internal Settings

▪Toggle features on and off

▪Change parameters

▪Trigger events

Future of iOS @ Facebook