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

Diffing inside SwiftUI List

Diffing inside SwiftUI List

2019/08/07 Bonfire iOS #6
Twitter hashtag: #yjbonfire

439ebe4787a98881df8a59d41baf4a43?s=128

Ryo Aoyama

August 07, 2019
Tweet

Transcript

  1. Diffing inside SwiftUI List 2019.08.07 WED 
 Bonfire iOS #6

    #yjbonfire Ryo Aoyama GitHub: @ra1028 Twitter: @ra1028fe5
  2. PROFILE Ryo Aoyama Cyberagent, Inc Ὂ CATS iOS Lead Ὂ

    WinTicket OSS Author Ὂ DifferenceKit, Carbon, VueFlux, etc… GitHub: @ra1028 Twitter: @ra1028fe5
  3. SwiftUI.List

  4. Current Status: iOS 13 beta 5 Xcode 11 beta 5

  5. SwiftUI.List 1 column N rowͰϨΠΞ΢τ͞ΕͨϦετͷView ࠩ෼ݕग़͞Εͨ෦෼ͷΈߋ৽ͯ͘͠ΕΔ ߋ৽͸ࠩ෼ʹ߹ΘͤͨΞχϝʔγϣϯ͕͔͔Δ ฏ͍ͨ͑͘͹UITableViewΛஔ͖׵͑ΔView

  6. SwiftUI಺Ͱͷࠩ෼ݕग़͸ͲͷΑ͏ʹ࣮૷͞Ε͍ͯΔ͔ ͋͘·ͰશͯϒϥοΫϘοΫεͳͷͰɺͰ͖Δ͚ͩ ͚ۙͮΔΑ͏ʹ૝૾ͯ͠ΈΔ

  7. Ծઆ: UITableViewDiffableDataSource ͕಺෦Ͱར༻͞Ε͍ͯΔʁ

  8. SwiftUIͱಉ͘͡iOS13͔Βಋೖ͞ΕΔUITableView༻ͷdata source applyϝιουͱNSDiffableDataSourceSnapshotΛ௨ͯࠩ͠෼ߋ৽͕ར༻Ͱ͖Δ UITableViewDiffableDataSource

  9. View Hierarchy͔Β୳ͬͯΈΔ

  10. struct ContentView: View { var body: some View { List(["A",

    "B", "C", "D"]) { text in Text(text) } } }
  11. View Hierarchy

  12. None
  13. let window = UIWindow(windowScene: windowScene) let controller = UIHostingController(rootView: ContentView())

    window.rootViewController = controller self.window = window window.makeKeyAndVisible() dump(controller.view)
  14. ▿ some: <_TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView: 0x7f92d586d800; baseClass = UITableView; frame = (0

    0; 375 812); clipsToBounds = YES; tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; gestureRecognizers = <NSArray: 0x600001eb0660>; layer = <CALayer: 0x6000010af340>; contentOffset: {0, -44}; contentSize: {375, 177.33333333333334}; adjustedContentInset: {44, 0, 34, 0}; dataSource: <_TtGC7SwiftUIP13$7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__: 0x7f92d3f13860>> #47 - super: UITableView - super: UIScrollView - super: UIView - super: UIResponder - super: NSObject Ұ෦ൈਮ
  15. swift demangle _TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView _TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView ---> SwiftUI.(UpdateCoalescingTableView in _BFB370BA5F1BADDC9D83021565761A49) swift demangle

    _TtGC7SwiftUIP13_7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__ _TtGC7SwiftUIP13_7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__ ---> SwiftUI.(ListCoreCoordinator in _7fff2c7391a8)<SwiftUI.SystemListDataSource<Swift.Never>, Swift.Never> demangle
  16. UpdateCoalescingTableViewͱ͍͏UITableViewܧঝclassΛར༻͍ͯͯ͠ ListCoreCoordinatorͱ͍͏class͕data sourceͱͯ͠ৼΔ෣͍ͬͯΔΑ͏ ࣮ࡍʹ͸ܕύϥϝʔλͷSystemListDataSourceͱ͍͏class͕data sourceͱͯ͠ ػೳ͍ͯͦ͠͏

  17. func extractTableView(from view: UIView) -> UITableView? { if let tableView

    = view as? UITableView { return tableView } for view in view.subviews { if let tableView = extractTableView(from: view) { return tableView } } return nil } dump(extractTableView(from: controller.view)?.dataSource)
  18. ▿ some: <_TtGC7SwiftUIP13$7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__: 0x7fd686e11490> #0 ▿ super: SwiftUI.PlatformViewCoordinator - super:

    NSObject ▿ dataSource: SwiftUI.SystemListDataSource<Swift.Never> ▿ sections: SwiftUI.Sections ▿ offsets: 1 element ... - hasExplicitSections: false - sectionsWithFooters: 0 members ▿ ids: ... ▿ super: ... ▿ base: ... Ұ෦ൈਮ
  19. ▿ updates: SwiftUI.ListCoreBatchUpdates<SwiftUI.SystemListDataSource<Swift.Never>> ▿ removeSections: 0 indexes - ranges: 0

    elements ▿ insertSections: 0 indexes - ranges: 0 elements - moveSections: 0 elements - removeRows: 0 elements - insertRows: 0 elements - moveRows: 0 elements - isEmpty: true ▿ base: [] - rawUpdates: 0 elements ▿ sectionChanges: [] - pathStorage: 0 elements - pathStartIndex: 0 ▿ rowChanges: [] - pathStorage: 0 elements - pathStartIndex: 0 - movedSectionCandidates: 0 key/value pairs - movedRowCandidates: 0 key/value pairs Ұ෦ൈਮ
  20. ListCoreCoordinatorͰܭࢉ͞ΕɺListCoreBatchUpdatesͱ͍͏஋Ͱ UpdateCoalescingTableViewͱSystemListDataSourceͷߋ৽Λߦ͍ͬͯΔʁ

  21. ViewGraph @State ListCoreCoordinator SystemListDataSource UpdateCoalescingTableView ListCoreBatchUpdates Update Diffing Apply Update

    Notify List Update
  22. ͋͘·Ͱ૝૾্ͷಈ࡞

  23. SwiftUI.Listͷࠩ෼ߋ৽͸UITableViewDiffableDataSource ʹґଘ͍ͯ͠ͳ͍ ࠩ෼ܭࢉํ๏͸୳ͬͨݶΓͰ͸·ͩΘ͔Βͳ͍͕ಉ࣌ظʹ࣮૷͞Εͨ UITableViewDiffableDataSourceͱࠜຊతʹҟͳΔख๏Ͱ͸ͳ͍͸ͣ…?

  24. Ͱ͸UITableViewDiffableDataSource͸ͲͷΑ͏ͳಈ࡞Λ͢Δͷ͔

  25. enum Section { case main } enum Item: String, CaseIterable

    { case a, b, c } @IBOutlet var tableView: UITableView! lazy var dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = item.rawValue return cell }
  26. enum Section { case main } enum Item: String, CaseIterable

    { case a, b, c } @IBOutlet var tableView: UITableView! lazy var dataSource = UITableViewDiffableDataSource<Section, Item>(tableView: tableView) { tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = item.rawValue return cell } SectionͱCellͷIDΛදݱ͢ΔܕͰHashableʹ ४ڌ͢Δඞཁ͕͋Δ
  27. Hashable͸ҰҙͰ͋Δ͜ͱΛ൑ผ͢ΔͨΊʹɺHash஋͸ॏෳ͕͋Γ͑Δͨ ΊEquatableͷ==ͷ݁Ռ΋ར༻͢Δ ͭ·Γɺࠩ෼ͱͯ͠͸delete, insertͷଞʹಉ͡IDͰ==ൺֱ݁Ռ͕ҟͳΔ΋ͷ ΛreloadΛऔΓ͍ͨͱ͜ΖΛγϯϓϧ͞ͷ୲อͷͨΊʹࣺ͍ͯͯΔ

  28. let snapshot = NSDiffableDataSourceSnapshot<Section, Item>() snapshot.appendSections([.main]) snapshot.appendItems(Item.allCases) dataSource.apply(snapshot)

  29. Snapshot͸appendSections/Items, deleteSections/Items, reloadSections/ Itemsͱ͍ͬͨϝιουͰ௥Ճɾ࡟আɾߋ৽Λߦ͏ ݱࡏͷdata sourceͷঢ়ଶΛؑΈΔඞཁ͸ͳ͘ɺຖճશମΛ࡞Γ௚ͯ͠΋UI͸ ࠩ෼ͷΈΛߋ৽͢Δ͜ͱ͕Ͱ͖Δ Snapshotͷมߋ࣌ʹࠩ෼৘ใΛอ࣋ͯ͠ΔͷͰ͸ͳ͘apply࣌ʹΞϧΰϦζϜ Ͱࠩ෼Λܭࢉ͍ͯ͠Δ

  30. Hashableͷಛੑ͔ΒΞϧΰϦζϜ͸reloadΛࣺ͍ͯͯΔ͸ͣ Ͱ͸reloadSections/Items͸ͲͷΑ͏ʹಈ࡞͢Δ͔

  31. struct Item: Hashable { var text: String var flag: Bool

    func hash(into hasher: inout Hasher) { hasher.combine(text) } static func == (lhs: Item, rhs: Item) -> Bool { lhs.text == rhs.text } } IDͱͯ͠ৼΔ෣͏஋(text)ͷଞʹ༨ܭͳ஋(flag)Λ΋ͭܕΛ࡞Γɺ Hashableͷ൑ఆ͔Βআ֎ͨ͠஋ΛSnapshotʹ௥Ճͯ͠ΈΔ (আ֎͠ͳ͍৔߹ͷࠩ෼͸delete + insertͱͯ͠ѻΘΕΔ) snapshot.appendItems([ Item(text: "A", flag: false), Item(text: "B", flag: false), Item(text: "C", flag: false) ])
  32. ౰વɺআ֎ͨ͠஋(flag)͕มΘͬͯ΋ࠩ෼ͱͯ͠ݕ஌͞Εͳ͍ͨΊUI͸ߋ৽͞Εͳ͍ ͜͜ͰSnapshotͷreloadItemsΛ”Ұ෦ͷItemʹͷΈ”࢖ͬͯΈΔ snapshot.reloadItems([ Item(text: "A", flag: true) ]) flagΛ൓స dataSource.apply(snapshot)

  33. ݁Ռ: reloadItemsʹ౉ͨ͠ItemͷΈߋ৽͞Εͨ ͭ·ΓɺreloadͷΈSnapshotͰreloadItem͞Εͨ஋ΛItemΛϚʔΫͯ͠ ࠩ෼ߋ৽࣌ʹΞϧΰϦζϜͰܭࢉͨࠩ͠෼ͱ͸ผͰߋ৽͢ΔΑ͏ͳ࣮૷ ʹͳ͍ͬͯΔΑ͏

  34. SwiftUI.Listͷreloadͷڍಈ͸ʁ

  35. public init<Data, RowContent>(_ data: Data, selection: Binding<Selection>?, action: @escaping (Data.Element.IdentifiedValue)

    -> Void, rowContent: @escaping (Data.Element.IdentifiedValue) -> RowContent) where Content == ForEach<Data, Button<HStack<RowContent>>>, Data: RandomAccessCollection, RowContent: View, Data.Element: Identifiable SwiftUI.List
  36. protocol Identifiable { associatedtype ID : Hashable var id: Self.ID

    { get } associatedtype IdentifiedValue = Self var identifiedValue: Self.IdentifiedValue { get } } Identifiable
  37. ஋ͦΕࣗମΛHashableʹ͢Δ͜ͱͳ͘IdentifiableͰͲͷ஋ΛIDͱͯ͠ར ༻͢Δ͔Λબ୒Ͱ͖Δ EquatableͰ͸ͳ͍ͷͰΞϧΰϦζϜͰreload͸औಘͤͣɺStateߋ৽࣌ ʹvisibleͳCellΛৗʹߋ৽͢Δ࢓༷ʹͳ͍ͬͯΔΑ͏

  38. ID͕ॏෳ͢Δͱʁ

  39. UITableViewDiffableDataSource NSDiffableDataSourceSnapshotʹ௥Ճͨ࣌͠఺Ͱɺॏෳ͠ ͨID͸҉໧తʹলུ͞ΕΔɺ΋͘͠͸Ϋϥογϡ

  40. SwiftUI.List ໰୊ͳ͠ ͨͩ͠ॏෳ͔ͭreloadͷඞཁ͕͋Δͱ͖ʹද͕ࣔ ޡΔ৔߹͕͋ΔͷͰॏෳͤ͞ͳ͍ͷ͕ϕλʔ ڪΒ͘όάͳͷͰࠓޙ௚͞ΕΔՄೳੑ΋͋Δ

  41. UITableViewDiffableDataSourceͱSwiftUI.List͸ࣅͨಛੑΛ΋͕ͭɺґଘؔ܎͸ ͳͦ͞͏ UITableViewDiffableDataSource͸SnapshotΛதܧ͢Δ͜ͱͰOOPͷੈքʹҧ࿨ ײͳ͘ɺ௚ײతʹࠩ෼ߋ৽Λ࣋ͪࠐΜͰ͍Δͷ͕ ͔͠͠૯߹తʹSwiftUI.Listͷํ͕ચ࿅͞Εͨ࢓༷Λ࣋ͭҹ৅ ͲͪΒ΋reload΍IDͷॏෳͳͲɺࡉ͔͍࢓༷Λཧղͯ͠ར༻͠ͳ͍ͱҙਤ͠ͳ͍ όάΛੜΜͰ͠·͏ͱ͜Ζ͕ͦΕͧΕ͋ΔͷͰ஫ҙ

  42. ΞϧΰϦζϜΛ༧૝ͯ͠ΈΔ

  43. Ordered Collection Diffing ? Ordered Collection Diffingͱ͸ Swift5.1Ͱར༻ՄೳʹͳΔػೳͰ഑ྻؒͷࠩ෼ΛऔಘͰ͖Δ https://github.com/apple/swift-evolution/blob/master/proposals/0240-ordered-collection-diffing.md

  44. Author Myers Wu Heckel Order O(ND) O(NP) O(N) Insert ✅

    ✅ ✅ Delete ✅ ✅ ✅ Move ⚠ ⚠ ✅ Update ❌ ❌ ✅ http://www.xmailserver.org/diff2.pdf http://documents.scribd.com/docs/10ro9oowpo1h81pgh1as.pdf https://publications.mpi-cbg.de/Wu_1990_6334.pdf Major Diff Algorithms
  45. Ordered Collection DiffingΞϧΰϦζϜͷϕʔε͸O(ND)ͷΦʔμʔΛ࣋ͭMyersࢯͷ࿦จΛݩʹͨ͠ ΋ͷͳͷͰɺ൚༻తͳػೳʹ͸ར༻͠೉͍ɻ ·ͨɺσϑΥϧτͰdelete, insertͷΈରԠͰɺmove΍ೋ࣍ݩ഑ྻͷࠩ෼ΛऔΔ৔߹ͷύϑΥʔϚϯε ʹݒ೦ɻ ͜ΕΒͷཧ༝ͰɺUI༻్ʹ͸ར༻͞Ε͍ͯͳ͍ͱࢥΘΕΔɻ ·ͨIDͷॏෳʹΑΔڍಈͷࠩҟͳͲͷಛ௃͔ΒɺPaul Heckelࢯͷ࿦จΛϕʔεʹͨ͠ΞϧΰϦζϜͱ

    ༧૝ɻ
  46. DifferenceKit Paul HeckelϕʔεͰ2D഑ྻؚΊͨશࠩ෼औಘͰ͖ΔΞϧΰϦζϜ https://github.com/ra1028/DifferenceKit Carbon SwiftUIͷΑ͏ͳએݴతͳγϯλοΫεͱࠩ෼ߋ৽ͰUIΛߏங https://github.com/ra1028/Carbon DiffableDataSources UITableViewDiffableDataSourceΛόοΫϙʔτ https://github.com/ra1028/DiffableDataSources

    Libraries based on diffing algorithm
  47. Thanks