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

9a2349983b8d0e1cd2ca8132daa3685a?s=47 Parveen Kaler
June 07, 2018
2.2k

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.

9a2349983b8d0e1cd2ca8132daa3685a?s=128

Parveen Kaler

June 07, 2018
Tweet

Transcript

  1. MILLIONS OF CUSTOMERS. BILLIONS OF DOLLARS. IOS ARCHITECTURE AT SCALE

    1 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  2. HOW LARGE? LARGE FORTUNE 500 COMPANY 2 — Parveen Kaler

    • @kaler • pk@smartfulstudios.com
  3. HOW LARGE? I AM ONLY GOING TO TALK ABOUT THE

    PRODUCT VIEW 3 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  4. 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 • pk@smartfulstudios.com
  5. 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 • pk@smartfulstudios.com
  6. HOW LEGACY? •Original code written in 2011 for iOS 5

    6 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  7. HOW LEGACY? •Original code written in 2011 for iOS 5

    •Before self-sizing UITableViewCells 7 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  8. HOW LEGACY? •Original code written in 2011 for iOS 5

    •Before self-sizing UITableViewCells •Before UICollectionView (iOS 6) 8 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  9. 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 • pk@smartfulstudios.com
  10. WHAT IS GOOD ARCHITECTURE? 10 — Parveen Kaler • @kaler

    • pk@smartfulstudios.com
  11. WHAT IS GOOD Architecture? A SET OF DECISIONS 11 —

    Parveen Kaler • @kaler • pk@smartfulstudios.com
  12. WHAT IS GOOD ARCHITECTURE? SMALL AND SIMPLE 12 — Parveen

    Kaler • @kaler • pk@smartfulstudios.com
  13. ! SIMPLE STATE MUTATION var heroImages: [URL] { didSet {

    preProcessImages() } } 13 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  14. SIMPLE DELEGATION weak var delegate: Tappable? @IBAction func somethingTapped(sender: UIButton)

    { delegate?.somethingTapped() } 14 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  15. SIMPLE CALLBACKS cell.somethingHappened = { [weak self] } 15 —

    Parveen Kaler • @kaler • pk@smartfulstudios.com
  16. SIMPLE OBSERVATION let observation = child.observe(\.productID) { (productID, change) in

    } 16 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  17. 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 • pk@smartfulstudios.com
  18. ! LESS FILES 18 — Parveen Kaler • @kaler •

    pk@smartfulstudios.com
  19. ! MORE FILES VIPER 19 — Parveen Kaler • @kaler

    • pk@smartfulstudios.com
  20. ! LOCAL STATE let, protocol, DEPENDENCY INJECTION 20 — Parveen

    Kaler • @kaler • pk@smartfulstudios.com
  21. ! GLOBAL STATE static, sharedInstance, SINGLETONS 21 — Parveen Kaler

    • @kaler • pk@smartfulstudios.com
  22. ! 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 • pk@smartfulstudios.com
  23. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift 23 —

    Parveen Kaler • @kaler • pk@smartfulstudios.com
  24. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular 24 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  25. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular •REST ➡ GraphQL 25 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  26. TRANSITIONING TO SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡

    Modular •REST ➡ GraphQL •UITableView ➡ UICollectionView 26 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  27. 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 • pk@smartfulstudios.com
  28. OBJECTIVE-C SWIFT •way less code •less language-level bugs (mostly) 28

    — Parveen Kaler • @kaler • pk@smartfulstudios.com
  29. 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 • pk@smartfulstudios.com
  30. 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 • pk@smartfulstudios.com
  31. OBJECTIVE-C SWIFT 31 — Parveen Kaler • @kaler • pk@smartfulstudios.com

  32. OBJECTIVE-C SWIFT SMALLER AND SIMPLER 32 — Parveen Kaler •

    @kaler • pk@smartfulstudios.com
  33. MONOLITHIC MODULAR •Plugins •Router 33 — Parveen Kaler • @kaler

    • pk@smartfulstudios.com
  34. A modular architecture makes each module smaller and simpler. 34

    — Parveen Kaler • @kaler • pk@smartfulstudios.com
  35. 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 • pk@smartfulstudios.com
  36. PLUGIN PROTOCOL A Supervisor manages access to other plugins. Supervisor.pluginAPI(forIdentifier:

    ProductDetailsID) as? ProductDetailsAPI 36 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  37. PLUGIN PROTOCOL Pass data or call a function. protocol ProductDetailsAPI

    { func productData(with productID: String) -> ProductData } 37 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  38. ROUTER Responsible for navigation and presenting views class Router {

    func register(route: Route) func routeTo(urlString: String) } 38 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  39. BACK TO THE PRODUCT MODULE 39 — Parveen Kaler •

    @kaler • pk@smartfulstudios.com
  40. LEGACY: CLIENT SIDE NETWORKING •written before NSURLSession •rethink: Objective-C blocks

    and GCD queue use 40 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  41. LEGACY: SERVER SIDE NETWORKING •web service originally targeted desktop web

    •some responses were very sloooooow •some responses were very faaaaaaat 41 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  42. NOW: GRAPHQL •ability to query for just the JSON that

    you need •fields are kinda typed 42 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  43. REST VS GRAPHQL •versioning is hard 43 — Parveen Kaler

    • @kaler • pk@smartfulstudios.com
  44. REST VS GRAPHQL •versioning is hard •POST breaks HTTP semantics

    44 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  45. REST VS GRAPHQL •versioning is hard •POST breaks HTTP semantics

    •caching is hard 45 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  46. REST VS GRAPHQL •versioning is hard •POST breaks HTTP semantics

    •caching is hard •there are really only 5 queries 46 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  47. FUTURE: NETWORKING •exploit GraphQL schema to optimize payload size •who

    owns networking? 47 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  48. WHO OWNS NETWORKING? •Right now the view model layer owns

    it •I think this is terrible 48 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  49. SERIALIZATION OBJECTIVE-C ➡ OBJECTMAPPER CODABLE 49 — Parveen Kaler •

    @kaler • pk@smartfulstudios.com
  50. OBJECTIVE-C @interface ProductModel: ModelObject { } @implementation ProductModel - (NSDictionary*)mappingDictionaryForData:(id)data

    { return @{ @"productID": mo_key(self.productID) }; } - (BOOL)isValidModel { } @end 50 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  51. OBJECTMAPPER class Product: Mappable { init?(map: Map) { } func

    mapping(map: Map) { } } 51 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  52. 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 • pk@smartfulstudios.com
  53. 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 • pk@smartfulstudios.com
  54. LEGACY CONTROLLER • ! Fat UITableViewController 54 — Parveen Kaler

    • @kaler • pk@smartfulstudios.com
  55. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols 55 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  56. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols • ! Picked the wrong abstraction: SectionControllers 56 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  57. LEGACY CONTROLLER • ! Fat UITableViewController • ! Conformed to

    too many protocols • ! Picked the wrong abstraction: SectionControllers • ! UITableView scrolling performance issues 57 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  58. 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 • pk@smartfulstudios.com
  59. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW 59 — Parveen Kaler •

    @kaler • pk@smartfulstudios.com
  60. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Modern API improves

    scroll performance • ! Less code 60 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  61. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW 61 — Parveen Kaler •

    @kaler • pk@smartfulstudios.com
  62. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Extract controller code

    into plain Swift objects 62 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  63. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Extract controller code

    into plain Swift objects • ! Extract UICollectionViewLayout, UICollectionViewLayoutAttributes 63 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  64. CURRENT CONTROLLER INITIATIVE UITABLEVIEWCONTROLLER UICOLLECTIONVIEW • ! Extract controller code

    into plain Swift objects • ! Extract UICollectionViewLayout, UICollectionViewLayoutAttributes • ! Extract UICollectionViewDataSource, UICollectionViewDelegate 64 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  65. SMALL AND SIMPLE •Objective-C ➡ Swift •Monolithic ➡ Modular •REST

    ➡ GraphQL •UITableView ➡ UICollectionView 65 — Parveen Kaler • @kaler • pk@smartfulstudios.com
  66. SMALL AND SIMPLE THANK YOU ! 66 — Parveen Kaler

    • @kaler • pk@smartfulstudios.com