Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Millions of Customers. Billions of Dollars: iOS Architecture at Scale

Parveen Kaler
June 07, 2018
2.3k

Millions of Customers. Billions of Dollars: iOS Architecture at Scale

I propose that Architecture is a set of decisions. And Good Architecture is a set of decisions that makes code small and simple.

Presented at AltConf 2018.

Parveen Kaler

June 07, 2018
Tweet

Transcript

  1. HOW LARGE? I AM ONLY GOING TO TALK ABOUT THE

    PRODUCT VIEW 3 — Parveen Kaler • @kaler • [email protected]
  2. HOW LARGE? Total Lines of Code LANGUAGE FILES CODE Swift

    2845 246570 Objective-C 1289 164414 C/C++ Header 1948 37311 Objective-C++ 47 16602 C 16 11861 JavaScript 78 7494 ... ... ... Total 6314 493367 4 — Parveen Kaler • @kaler • [email protected]
  3. HOW LARGE? Product Module Lines of Code LANGUAGE FILES CODE

    Swift 327 25670 Objective-C 84 16274 GraphQL 9 3004 C/C++ Header 86 1405 JavaScript 1 24 Total 507 46377 5 — Parveen Kaler • @kaler • [email protected]
  4. HOW LEGACY? •Original code written in 2011 for iOS 5

    •Before self-sizing UITableViewCells 7 — Parveen Kaler • @kaler • [email protected]
  5. HOW LEGACY? •Original code written in 2011 for iOS 5

    •Before self-sizing UITableViewCells •Before UICollectionView (iOS 6) 8 — Parveen Kaler • @kaler • [email protected]
  6. HOW LEGACY? •Original code written in 2011 for iOS 5

    •Before self-sizing UITableViewCells •Before UICollectionView (iOS 6) •Before Auto Layout (iOS 6) 9 — Parveen Kaler • @kaler • [email protected]
  7. ! SIMPLE STATE MUTATION var heroImages: [URL] { didSet {

    preProcessImages() } } 13 — Parveen Kaler • @kaler • [email protected]
  8. COMPLEX DATA FLOW REACTIVE PROGRAMMING self.selectedIndex = Signal.combineLatest( .merge( self.didSelectIndexProperty.signal,

    self.switchToActivitiesProperty.signal.mapConst(1), self.switchToDiscoveryProperty.signal.mapConst(0), self.switchToSearchProperty.signal.mapConst(2), switchToLogin, switchToProfile, self.switchToDashboardProperty.signal.mapConst(3) ), self.setViewControllers, self.viewDidLoadProperty.signal) .map { idx, vcs, _ in clamp(0, vcs.count - 1)(idx) } 17 — Parveen Kaler • @kaler • [email protected]
  9. ! DEBUGABILITY1 AND SMALL CALL STACKS 1 What does that

    even mean? No one knows what it means, but it's provocative. 22 — Parveen Kaler • @kaler • [email protected]
  10. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular •REST ➡ GraphQL 25 — Parveen Kaler • @kaler • [email protected]
  11. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular •REST ➡ GraphQL •UITableView ➡ UICollectionView 26 — Parveen Kaler • @kaler • [email protected]
  12. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular •REST ➡ GraphQL •UITableView ➡ UICollectionView •Oh BTW, there was a redesign along the way, too 27 — Parveen Kaler • @kaler • [email protected]
  13. OBJECTIVE-C BUGS •nil Unexpected data from web service resulting in

    silent failure •@optional protocols and respondsToSelector Products may have complex optional functionality •Just a whole bunch of complexity from having a whole lot of code 29 — Parveen Kaler • @kaler • [email protected]
  14. SWIFT BUGS •implicitly unwrapped optionals •force unwrapping3 We were learning

    Swift on the fly. We made mistakes. 3 guard and defer were added in Swift 2.0 30 — Parveen Kaler • @kaler • [email protected]
  15. PLUGIN PROTOCOL protocol Pluggable { var identifier: String var dependencies:

    [String] func startup(_ supervisor: Supervisor) } Also forwards all UIApplicationDelegate messages to each plugin 35 — Parveen Kaler • @kaler • [email protected]
  16. PLUGIN PROTOCOL A Supervisor manages access to other plugins. Supervisor.pluginAPI(forIdentifier:

    ProductDetailsID) as? ProductDetailsAPI 36 — Parveen Kaler • @kaler • [email protected]
  17. PLUGIN PROTOCOL Pass data or call a function. protocol ProductDetailsAPI

    { func productData(with productID: String) -> ProductData } 37 — Parveen Kaler • @kaler • [email protected]
  18. ROUTER Responsible for navigation and presenting views class Router {

    func register(route: Route) func routeTo(urlString: String) } 38 — Parveen Kaler • @kaler • [email protected]
  19. LEGACY: SERVER SIDE NETWORKING •web service originally targeted desktop web

    •some responses were very sloooooow •some responses were very faaaaaaat 41 — Parveen Kaler • @kaler • [email protected]
  20. NOW: GRAPHQL •ability to query for just the JSON that

    you need •fields are kinda typed 42 — Parveen Kaler • @kaler • [email protected]
  21. REST VS GRAPHQL •versioning is hard •POST breaks HTTP semantics

    •caching is hard 45 — Parveen Kaler • @kaler • [email protected]
  22. REST VS GRAPHQL •versioning is hard •POST breaks HTTP semantics

    •caching is hard •there are really only 5 queries 46 — Parveen Kaler • @kaler • [email protected]
  23. WHO OWNS NETWORKING? •Right now the view model layer owns

    it •I think this is terrible 48 — Parveen Kaler • @kaler • [email protected]
  24. OBJECTIVE-C @interface ProductModel: ModelObject { } @implementation ProductModel - (NSDictionary*)mappingDictionaryForData:(id)data

    { return @{ @"productID": mo_key(self.productID) }; } - (BOOL)isValidModel { } @end 50 — Parveen Kaler • @kaler • [email protected]
  25. OBJECTMAPPER class Product: Mappable { init?(map: Map) { } func

    mapping(map: Map) { } } 51 — Parveen Kaler • @kaler • [email protected]
  26. CODABLE class Product: Codable { var productID: Int enum CodingKeys:

    String, CodingKey { case productID } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.productID = try container.decode(Int.self, forKey: .productID) } } 52 — Parveen Kaler • @kaler • [email protected]
  27. CODABLE THE TOOLCHAIN MAY GENERATE CODE FOR YOU: •Type conforms

    to Codable •Each property conforms to Codable class Product: Codable { var productID: Int } 53 — Parveen Kaler • @kaler • [email protected]
  28. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols 55 — Parveen Kaler • @kaler • [email protected]
  29. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols • ! Picked the wrong abstraction: SectionControllers 56 — Parveen Kaler • @kaler • [email protected]
  30. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols • ! Picked the wrong abstraction: SectionControllers • ! UITableView scrolling performance issues 57 — Parveen Kaler • @kaler • [email protected]
  31. CURRENT CONTROLLER INITIATIVE OBJECTIVE-C SWIFT class ProductViewController: UIViewController { }

    extension ProductViewController: MFMessageComposeViewControllerDelegate { } • ! Strategic use of extension to break out protocol conformance 58 — Parveen Kaler • @kaler • [email protected]
  32. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Extract controller code

    into plain Swift objects • ! Extract UICollectionViewLayout, UICollectionViewLayoutAttributes 63 — Parveen Kaler • @kaler • [email protected]
  33. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Extract controller code

    into plain Swift objects • ! Extract UICollectionViewLayout, UICollectionViewLayoutAttributes • ! Extract UICollectionViewDataSource, UICollectionViewDelegate 64 — Parveen Kaler • @kaler • [email protected]
  34. SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡ Modular •REST

    ➡ GraphQL •UITableView ➡ UICollectionView 65 — Parveen Kaler • @kaler • [email protected]