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

Millions of Customers. Billions of Dollars: iOS...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Parveen Kaler Parveen Kaler
June 07, 2018
2.5k

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.

Avatar for Parveen Kaler

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]