Slide 1

Slide 1 text

Diffing inside SwiftUI List 2019.08.07 WED 
 Bonfire iOS #6 #yjbonfire Ryo Aoyama GitHub: @ra1028 Twitter: @ra1028fe5

Slide 2

Slide 2 text

PROFILE Ryo Aoyama Cyberagent, Inc Ὂ CATS iOS Lead Ὂ WinTicket OSS Author Ὂ DifferenceKit, Carbon, VueFlux, etc… GitHub: @ra1028 Twitter: @ra1028fe5

Slide 3

Slide 3 text

SwiftUI.List

Slide 4

Slide 4 text

Current Status: iOS 13 beta 5 Xcode 11 beta 5

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

View Hierarchy͔Β୳ͬͯΈΔ

Slide 10

Slide 10 text

struct ContentView: View { var body: some View { List(["A", "B", "C", "D"]) { text in Text(text) } } }

Slide 11

Slide 11 text

View Hierarchy

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

let window = UIWindow(windowScene: windowScene) let controller = UIHostingController(rootView: ContentView()) window.rootViewController = controller self.window = window window.makeKeyAndVisible() dump(controller.view)

Slide 14

Slide 14 text

▿ some: <_TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView: 0x7f92d586d800; baseClass = UITableView; frame = (0 0; 375 812); clipsToBounds = YES; tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; gestureRecognizers = ; layer = ; 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 Ұ෦ൈਮ

Slide 15

Slide 15 text

swift demangle _TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView _TtC7SwiftUIP33_BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView ---> SwiftUI.(UpdateCoalescingTableView in _BFB370BA5F1BADDC9D83021565761A49) swift demangle _TtGC7SwiftUIP13_7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__ _TtGC7SwiftUIP13_7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__ ---> SwiftUI.(ListCoreCoordinator in _7fff2c7391a8), Swift.Never> demangle

Slide 16

Slide 16 text

UpdateCoalescingTableViewͱ͍͏UITableViewܧঝclassΛར༻͍ͯͯ͠ ListCoreCoordinatorͱ͍͏class͕data sourceͱͯ͠ৼΔ෣͍ͬͯΔΑ͏ ࣮ࡍʹ͸ܕύϥϝʔλͷSystemListDataSourceͱ͍͏class͕data sourceͱͯ͠ ػೳ͍ͯͦ͠͏

Slide 17

Slide 17 text

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)

Slide 18

Slide 18 text

▿ some: <_TtGC7SwiftUIP13$7fff2c7391a819ListCoreCoordinatorGVS_20SystemListDataSourceOs5Never_S2__: 0x7fd686e11490> #0 ▿ super: SwiftUI.PlatformViewCoordinator - super: NSObject ▿ dataSource: SwiftUI.SystemListDataSource ▿ sections: SwiftUI.Sections ▿ offsets: 1 element ... - hasExplicitSections: false - sectionsWithFooters: 0 members ▿ ids: ... ▿ super: ... ▿ base: ... Ұ෦ൈਮ

Slide 19

Slide 19 text

▿ updates: SwiftUI.ListCoreBatchUpdates> ▿ 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 Ұ෦ൈਮ

Slide 20

Slide 20 text

ListCoreCoordinatorͰܭࢉ͞ΕɺListCoreBatchUpdatesͱ͍͏஋Ͱ UpdateCoalescingTableViewͱSystemListDataSourceͷߋ৽Λߦ͍ͬͯΔʁ

Slide 21

Slide 21 text

ViewGraph @State ListCoreCoordinator SystemListDataSource UpdateCoalescingTableView ListCoreBatchUpdates Update Diffing Apply Update Notify List Update

Slide 22

Slide 22 text

͋͘·Ͱ૝૾্ͷಈ࡞

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

enum Section { case main } enum Item: String, CaseIterable { case a, b, c } @IBOutlet var tableView: UITableView! lazy var dataSource = UITableViewDiffableDataSource(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ʹ ४ڌ͢Δඞཁ͕͋Δ

Slide 27

Slide 27 text

Hashable͸ҰҙͰ͋Δ͜ͱΛ൑ผ͢ΔͨΊʹɺHash஋͸ॏෳ͕͋Γ͑Δͨ ΊEquatableͷ==ͷ݁Ռ΋ར༻͢Δ ͭ·Γɺࠩ෼ͱͯ͠͸delete, insertͷଞʹಉ͡IDͰ==ൺֱ݁Ռ͕ҟͳΔ΋ͷ ΛreloadΛऔΓ͍ͨͱ͜ΖΛγϯϓϧ͞ͷ୲อͷͨΊʹࣺ͍ͯͯΔ

Slide 28

Slide 28 text

let snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) snapshot.appendItems(Item.allCases) dataSource.apply(snapshot)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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) ])

Slide 32

Slide 32 text

౰વɺআ֎ͨ͠஋(flag)͕มΘͬͯ΋ࠩ෼ͱͯ͠ݕ஌͞Εͳ͍ͨΊUI͸ߋ৽͞Εͳ͍ ͜͜ͰSnapshotͷreloadItemsΛ”Ұ෦ͷItemʹͷΈ”࢖ͬͯΈΔ snapshot.reloadItems([ Item(text: "A", flag: true) ]) flagΛ൓స dataSource.apply(snapshot)

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

SwiftUI.Listͷreloadͷڍಈ͸ʁ

Slide 35

Slide 35 text

public init(_ data: Data, selection: Binding?, action: @escaping (Data.Element.IdentifiedValue) -> Void, rowContent: @escaping (Data.Element.IdentifiedValue) -> RowContent) where Content == ForEach>>, Data: RandomAccessCollection, RowContent: View, Data.Element: Identifiable SwiftUI.List

Slide 36

Slide 36 text

protocol Identifiable { associatedtype ID : Hashable var id: Self.ID { get } associatedtype IdentifiedValue = Self var identifiedValue: Self.IdentifiedValue { get } } Identifiable

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

ID͕ॏෳ͢Δͱʁ

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Ordered Collection DiffingΞϧΰϦζϜͷϕʔε͸O(ND)ͷΦʔμʔΛ࣋ͭMyersࢯͷ࿦จΛݩʹͨ͠ ΋ͷͳͷͰɺ൚༻తͳػೳʹ͸ར༻͠೉͍ɻ ·ͨɺσϑΥϧτͰdelete, insertͷΈରԠͰɺmove΍ೋ࣍ݩ഑ྻͷࠩ෼ΛऔΔ৔߹ͷύϑΥʔϚϯε ʹݒ೦ɻ ͜ΕΒͷཧ༝ͰɺUI༻్ʹ͸ར༻͞Ε͍ͯͳ͍ͱࢥΘΕΔɻ ·ͨIDͷॏෳʹΑΔڍಈͷࠩҟͳͲͷಛ௃͔ΒɺPaul Heckelࢯͷ࿦จΛϕʔεʹͨ͠ΞϧΰϦζϜͱ ༧૝ɻ

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Thanks