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

Ryan Nystrom: Refactoring at scale – Lessons learned rewriting Instagram’s feed

1fa9cb8c7997c8c4d3d251fb5e41f749?s=47 Realm
September 02, 2016

Ryan Nystrom: Refactoring at scale – Lessons learned rewriting Instagram’s feed

Ryan is a lead iOS engineer at Instagram working on app infrastructure in New York City. He is an avid open source advocate and contributor at Facebook on projects like AsyncDisplayKit. Ryan is also an author and presenter with RayWenderlich.com publishing work on the Apple Watch, 3D Touch, and Reactive Cocoa.

Abstract:
When apps grow, there eventually comes a time where you hear the dreaded word “refactor”. For established apps, refactors present interesting problems from the amount of code and teams involved. Not to mention performance and testing implications on a large application.

But no matter the size, there are common problems that any app runs into when taking on a big refactor. Things like massive view controllers, dependencies, and managing goals and priorities.

Come learn about how and why the Instagram team took on rewriting their iOS feed from the bottom up, and see what it takes to ship a successful refactor.

Twitter: https://twitter.com/_ryannystrom

1fa9cb8c7997c8c4d3d251fb5e41f749?s=128

Realm

September 02, 2016
Tweet

Transcript

  1. REBUILDING Ryan Nystrom @_ryannystrom

  2. TECH DEBT Image source: Reddit user u/Pracy_

  3. None
  4. None
  5. Cells Header 0 1 2 3 4 5 6

  6. Feed Item

  7. None
  8. ☹ +

  9. Feed Item Video User

  10. Feed Item Video User

  11. Feed Item

  12. Feed Item

  13. Feed Item

  14. Feed Item

  15. Feed Item Feed Item Feed Item Feed Item Feed Item

  16. Feed Item Feed Item Feed Item Feed Item Feed Item

  17. Feed Feed Item Feed Item Feed Item Feed Item Feed

    Item
  18. Feed Feed Item Feed Item Feed Item Feed Item Feed

    Item
  19. Collections Networking Main Feed Feed Feed Item Feed Item Feed

    Item Feed Item Feed Item
  20. M V C

  21. Massive View Controller

  22. What’s the worst that can happen?

  23. None
  24. FEED 2.0 Image source: Reddit user u/KetoEater

  25. 1 2 5 3 4 6 7 8 From

  26. 1 cat 3 4 7 8 9 10 1 2

    5 3 4 6 7 8 From To
  27. 2 6 2 6 1 5 3 4 7 8

    1 5 3 4 7 8 1 cat 3 4 7 8 9 10 Delete 2, 6
  28. cat 5 1 2 3 4 6 7 8 1

    3 4 7 8 1 cat 3 4 7 8 9 10 Reload 5
  29. 1 2 5 3 4 6 7 8 1 cat

    3 4 7 8 1 cat 3 4 7 8 9 10 Move
  30. 1 2 5 3 4 6 7 8 Insert 9,

    10 1 cat 3 4 7 8 9 10 9 10
  31. Diffing using Least Common Subsequence Finds all deletes, reloads, inserts,

    moves O(n) complexity Image source: The Internet
  32. Collections Networking Main Feed Feed Feed Item Feed Item Feed

    Item Feed Item Feed Item
  33. Feed Feed Item Feed Item Feed Item Feed Item Feed

    Item
  34. Feed Item Feed Item Feed Item Feed Item Feed Item

    “The World”
  35. Feed Item Feed Item Feed Item Feed Item Feed Item

  36. Feed Item “Item Controller”

  37. Feed Item “Item Controller” • Number of items • Configure

    cells • Cell size • Interaction • Business logic
  38. Feed Item Feed Item Feed Item Feed Item

  39. Feed Item Feed Item Feed Item Feed Item Any Object

  40. +

  41. None
  42. Image source: Reddit user u/orbojunglist OPEN SOURCE

  43. • Subclass UIViewController • Conform to UICollectionViewDataSource • Register cells

    • Return number of sections • Return number of items • Return cells • Return cell size • Insert select items • Delete others • Try to move… CREATING A UICollectionView FEED
  44. • Subclass UIViewController • Conform to UICollectionViewDataSource • Register cells

    • Return number of sections • Return number of items • Return cells • Return cell size • Insert select items • Delete others • Try to move… • Just reloadData CREATING A UICollectionView FEED
  45. What can we give back?

  46. IGListKit

  47. IGItemController

  48. class LabelItemController: IGListItemController, IGListItemType { var item: String? func numberOfItems()

    -> UInt { return 1 } func sizeForItemAtIndex(index: Int) -> CGSize { return CGSize(width: collectionContext!.containerSize.width, height: 55) } func cellForItemAtIndex(index: Int) -> UICollectionViewCell { let cell = collectionContext!.dequeReusableCellOfClass(LabelCell.self, forItemController: self, atIndex: index) as! LabelCell cell.label.text = item return cell } func didUpdateToItem(item: AnyObject) { self.item = item as? String } func didSelectItemAtIndex(index: Int) {} func didDeselectItemAtIndex(index: Int) {} }
  49. class LabelItemController: IGListItemController, IGListItemType { ... }

  50. func numberOfItems() -> UInt { return 1 }

  51. func sizeForItemAtIndex(index: Int) -> CGSize { return CGSize(width: collectionContext!.containerSize.width, height:

    55) }
  52. var item: String? func didUpdateToItem(item: AnyObject) { self.item = item

    as? String }
  53. func cellForItemAtIndex(index: Int) -> UICollectionViewCell { let cell = collectionContext!.dequeReusableCellOfClass(LabelCell.self,

    forItemController: self, atIndex: index) as! LabelCell cell.label.text = item return cell }
  54. class LabelItemController: IGListItemController, IGListItemType { var item: String? func numberOfItems()

    -> UInt { return 1 } func sizeForItemAtIndex(index: Int) -> CGSize { return CGSize(width: collectionContext!.containerSize.width, height: 55) } func cellForItemAtIndex(index: Int) -> UICollectionViewCell { let cell = collectionContext!.dequeReusableCellOfClass(LabelCell.self, forItemController: self, atIndex: index) as! LabelCell cell.label.text = item return cell } func didUpdateToItem(item: AnyObject) { self.item = item as? String } func didSelectItemAtIndex(index: Int) {} func didDeselectItemAtIndex(index: Int) {} }
  55. IGListAdapter

  56. //MARK: IGListAdapterDataSource func itemsForListAdapter(listAdapter: IGListAdapter) -> [IGListDiffable] { return [

    "Foo", "Bar", "Baz" ] } func listAdapter(listAdapter: IGListAdapter, itemControllerForItem item: AnyObject) -> IGListItemController { return LabelItemController() }
  57. func itemsForListAdapter(listAdapter: IGListAdapter) -> [IGListDiffable] { return [ "Foo", "Bar",

    "Baz" ] }
  58. func listAdapter(listAdapter: IGListAdapter, itemControllerForItem item: AnyObject) -> IGListItemController { return

    LabelItemController() }
  59. None
  60. let spinToken = NSObject() func itemsForListAdapter(listAdapter: IGListAdapter) -> [IGListDiffable] {

    return [ "Foo", “Bar", spinToken, “Baz” ] }
  61. func listAdapter(listAdapter: IGListAdapter, itemControllerForItem item: AnyObject) -> IGListItemController { if

    item === spinToken { return SpinnerItemController() } else { return LabelItemController() } }
  62. None
  63. func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { filterString = text

    adapter.performUpdatesAnimated(true, completion: nil) }
  64. let words = [ “Foo", “Bar", “Baz" ] func itemsForListAdapter(listAdapter:

    IGListAdapter) -> [IGListDiffable] { return words.filter { word in return word.containsString(filterString) } }
  65. None
  66. Why should I use IGListKit?

  67. • Feed with multiple data types • Fast, crash-free, animated

    updates • Reusable components • Never performBatchUpdates or reloadData WHY SHOULD I USE IGLISTKIT?
  68. IGListKit Supplementary views Display events Working ranges Unit tested Navigation

    support Visible item controllers Stacked item controller Single-cell item controller Empty background view iOS 8+ Custom layouts Experiments Extendable Examples Tutorials
  69. 3.9M diffs since this talk began

  70. None
  71. None
  72. COMING THIS MONT github.com/instagram/IGListKit

  73. None