Diff.swift 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. //
  2. // Differentiator.swift
  3. // RxDataSources
  4. //
  5. // Created by Krunoslav Zaher on 6/27/15.
  6. // Copyright © 2015 Krunoslav Zaher. All rights reserved.
  7. //
  8. import Foundation
  9. fileprivate extension AnimatableSectionModelType {
  10. init(safeOriginal: Self, safeItems: [Item]) throws {
  11. self.init(original: safeOriginal, items: safeItems)
  12. if self.items != safeItems || self.identity != safeOriginal.identity {
  13. throw Diff.Error.invalidInitializerImplementation(section: self, expectedItems: safeItems, expectedIdentifier: safeOriginal.identity)
  14. }
  15. }
  16. }
  17. public enum Diff {
  18. public enum Error : Swift.Error, CustomDebugStringConvertible {
  19. case duplicateItem(item: Any)
  20. case duplicateSection(section: Any)
  21. case invalidInitializerImplementation(section: Any, expectedItems: Any, expectedIdentifier: Any)
  22. public var debugDescription: String {
  23. switch self {
  24. case let .duplicateItem(item):
  25. return "Duplicate item \(item)"
  26. case let .duplicateSection(section):
  27. return "Duplicate section \(section)"
  28. case let .invalidInitializerImplementation(section, expectedItems, expectedIdentifier):
  29. return "Wrong initializer implementation for: \(section)\n" +
  30. "Expected it should return items: \(expectedItems)\n" +
  31. "Expected it should have id: \(expectedIdentifier)"
  32. }
  33. }
  34. }
  35. private enum EditEvent : CustomDebugStringConvertible {
  36. case inserted // can't be found in old sections
  37. case insertedAutomatically // Item inside section being inserted
  38. case deleted // Was in old, not in new, in it's place is something "not new" :(, otherwise it's Updated
  39. case deletedAutomatically // Item inside section that is being deleted
  40. case moved // same item, but was on different index, and needs explicit move
  41. case movedAutomatically // don't need to specify any changes for those rows
  42. case untouched
  43. var debugDescription: String {
  44. switch self {
  45. case .inserted:
  46. return "Inserted"
  47. case .insertedAutomatically:
  48. return "InsertedAutomatically"
  49. case .deleted:
  50. return "Deleted"
  51. case .deletedAutomatically:
  52. return "DeletedAutomatically"
  53. case .moved:
  54. return "Moved"
  55. case .movedAutomatically:
  56. return "MovedAutomatically"
  57. case .untouched:
  58. return "Untouched"
  59. }
  60. }
  61. }
  62. private struct SectionAssociatedData : CustomDebugStringConvertible {
  63. var event: EditEvent
  64. var indexAfterDelete: Int?
  65. var moveIndex: Int?
  66. var itemCount: Int
  67. var debugDescription: String {
  68. return "\(event), \(String(describing: indexAfterDelete))"
  69. }
  70. static var initial: SectionAssociatedData {
  71. return SectionAssociatedData(event: .untouched, indexAfterDelete: nil, moveIndex: nil, itemCount: 0)
  72. }
  73. }
  74. private struct ItemAssociatedData: CustomDebugStringConvertible {
  75. var event: EditEvent
  76. var indexAfterDelete: Int?
  77. var moveIndex: ItemPath?
  78. var debugDescription: String {
  79. return "\(event) \(String(describing: indexAfterDelete))"
  80. }
  81. static var initial : ItemAssociatedData {
  82. return ItemAssociatedData(event: .untouched, indexAfterDelete: nil, moveIndex: nil)
  83. }
  84. }
  85. private static func indexSections<Section: AnimatableSectionModelType>(_ sections: [Section]) throws -> [Section.Identity : Int] {
  86. var indexedSections: [Section.Identity : Int] = [:]
  87. for (i, section) in sections.enumerated() {
  88. guard indexedSections[section.identity] == nil else {
  89. #if DEBUG
  90. if indexedSections[section.identity] != nil {
  91. print("Section \(section) has already been indexed at \(indexedSections[section.identity]!)")
  92. }
  93. #endif
  94. throw Error.duplicateSection(section: section)
  95. }
  96. indexedSections[section.identity] = i
  97. }
  98. return indexedSections
  99. }
  100. //================================================================================
  101. // Optimizations because Swift dictionaries are extremely slow (ARC, bridging ...)
  102. //================================================================================
  103. // swift dictionary optimizations {
  104. private struct OptimizedIdentity<Identity: Hashable> : Hashable {
  105. let identity: UnsafePointer<Identity>
  106. private let cachedHashValue: Int
  107. init(_ identity: UnsafePointer<Identity>) {
  108. self.identity = identity
  109. self.cachedHashValue = identity.pointee.hashValue
  110. }
  111. func hash(into hasher: inout Hasher) {
  112. hasher.combine(self.cachedHashValue)
  113. }
  114. static func == (lhs: OptimizedIdentity<Identity>, rhs: OptimizedIdentity<Identity>) -> Bool {
  115. if lhs.hashValue != rhs.hashValue {
  116. return false
  117. }
  118. if lhs.identity.distance(to: rhs.identity) == 0 {
  119. return true
  120. }
  121. return lhs.identity.pointee == rhs.identity.pointee
  122. }
  123. }
  124. private static func calculateAssociatedData<Item: IdentifiableType>(
  125. initialItemCache: ContiguousArray<ContiguousArray<Item>>,
  126. finalItemCache: ContiguousArray<ContiguousArray<Item>>
  127. ) throws
  128. -> (ContiguousArray<ContiguousArray<ItemAssociatedData>>, ContiguousArray<ContiguousArray<ItemAssociatedData>>) {
  129. // swiftlint:disable:next nesting
  130. typealias Identity = Item.Identity
  131. let totalInitialItems = initialItemCache.map { $0.count }.reduce(0, +)
  132. var initialIdentities: ContiguousArray<Identity> = ContiguousArray()
  133. var initialItemPaths: ContiguousArray<ItemPath> = ContiguousArray()
  134. initialIdentities.reserveCapacity(totalInitialItems)
  135. initialItemPaths.reserveCapacity(totalInitialItems)
  136. for (i, items) in initialItemCache.enumerated() {
  137. for j in 0 ..< items.count {
  138. let item = items[j]
  139. initialIdentities.append(item.identity)
  140. initialItemPaths.append(ItemPath(sectionIndex: i, itemIndex: j))
  141. }
  142. }
  143. var initialItemData = ContiguousArray(initialItemCache.map { items in
  144. return ContiguousArray<ItemAssociatedData>(repeating: ItemAssociatedData.initial, count: items.count)
  145. })
  146. var finalItemData = ContiguousArray(finalItemCache.map { items in
  147. return ContiguousArray<ItemAssociatedData>(repeating: ItemAssociatedData.initial, count: items.count)
  148. })
  149. try initialIdentities.withUnsafeBufferPointer { (identitiesBuffer: UnsafeBufferPointer<Identity>) -> Void in
  150. var dictionary: [OptimizedIdentity<Identity>: Int] = Dictionary(minimumCapacity: totalInitialItems * 2)
  151. for i in 0 ..< initialIdentities.count {
  152. let identityPointer = identitiesBuffer.baseAddress!.advanced(by: i)
  153. let key = OptimizedIdentity(identityPointer)
  154. if let existingValueItemPathIndex = dictionary[key] {
  155. let itemPath = initialItemPaths[existingValueItemPathIndex]
  156. let item = initialItemCache[itemPath.sectionIndex][itemPath.itemIndex]
  157. #if DEBUG
  158. print("Item \(item) has already been indexed at \(itemPath)" )
  159. #endif
  160. throw Error.duplicateItem(item: item)
  161. }
  162. dictionary[key] = i
  163. }
  164. for (i, items) in finalItemCache.enumerated() {
  165. for j in 0 ..< items.count {
  166. let item = items[j]
  167. var identity = item.identity
  168. let key = OptimizedIdentity(&identity)
  169. guard let initialItemPathIndex = dictionary[key] else {
  170. continue
  171. }
  172. let itemPath = initialItemPaths[initialItemPathIndex]
  173. if initialItemData[itemPath.sectionIndex][itemPath.itemIndex].moveIndex != nil {
  174. throw Error.duplicateItem(item: item)
  175. }
  176. initialItemData[itemPath.sectionIndex][itemPath.itemIndex].moveIndex = ItemPath(sectionIndex: i, itemIndex: j)
  177. finalItemData[i][j].moveIndex = itemPath
  178. }
  179. }
  180. return ()
  181. }
  182. return (initialItemData, finalItemData)
  183. }
  184. // } swift dictionary optimizations
  185. /*
  186. I've uncovered this case during random stress testing of logic.
  187. This is the hardest generic update case that causes two passes, first delete, and then move/insert
  188. [
  189. NumberSection(model: "1", items: [1111]),
  190. NumberSection(model: "2", items: [2222]),
  191. ]
  192. [
  193. NumberSection(model: "2", items: [0]),
  194. NumberSection(model: "1", items: []),
  195. ]
  196. If update is in the form
  197. * Move section from 2 to 1
  198. * Delete Items at paths 0 - 0, 1 - 0
  199. * Insert Items at paths 0 - 0
  200. or
  201. * Move section from 2 to 1
  202. * Delete Items at paths 0 - 0
  203. * Reload Items at paths 1 - 0
  204. or
  205. * Move section from 2 to 1
  206. * Delete Items at paths 0 - 0
  207. * Reload Items at paths 0 - 0
  208. it crashes table view.
  209. No matter what change is performed, it fails for me.
  210. If anyone knows how to make this work for one Changeset, PR is welcome.
  211. */
  212. // If you are considering working out your own algorithm, these are tricky
  213. // transition cases that you can use.
  214. // case 1
  215. /*
  216. from = [
  217. NumberSection(model: "section 4", items: [10, 11, 12]),
  218. NumberSection(model: "section 9", items: [25, 26, 27]),
  219. ]
  220. to = [
  221. HashableSectionModel(model: "section 9", items: [11, 26, 27]),
  222. HashableSectionModel(model: "section 4", items: [10, 12])
  223. ]
  224. */
  225. // case 2
  226. /*
  227. from = [
  228. HashableSectionModel(model: "section 10", items: [26]),
  229. HashableSectionModel(model: "section 7", items: [5, 29]),
  230. HashableSectionModel(model: "section 1", items: [14]),
  231. HashableSectionModel(model: "section 5", items: [16]),
  232. HashableSectionModel(model: "section 4", items: []),
  233. HashableSectionModel(model: "section 8", items: [3, 15, 19, 23]),
  234. HashableSectionModel(model: "section 3", items: [20])
  235. ]
  236. to = [
  237. HashableSectionModel(model: "section 10", items: [26]),
  238. HashableSectionModel(model: "section 1", items: [14]),
  239. HashableSectionModel(model: "section 9", items: [3]),
  240. HashableSectionModel(model: "section 5", items: [16, 8]),
  241. HashableSectionModel(model: "section 8", items: [15, 19, 23]),
  242. HashableSectionModel(model: "section 3", items: [20]),
  243. HashableSectionModel(model: "Section 2", items: [7])
  244. ]
  245. */
  246. // case 3
  247. /*
  248. from = [
  249. HashableSectionModel(model: "section 4", items: [5]),
  250. HashableSectionModel(model: "section 6", items: [20, 14]),
  251. HashableSectionModel(model: "section 9", items: []),
  252. HashableSectionModel(model: "section 2", items: [2, 26]),
  253. HashableSectionModel(model: "section 8", items: [23]),
  254. HashableSectionModel(model: "section 10", items: [8, 18, 13]),
  255. HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19])
  256. ]
  257. to = [
  258. HashableSectionModel(model: "section 4", items: [5]),
  259. HashableSectionModel(model: "section 6", items: [20, 14]),
  260. HashableSectionModel(model: "section 9", items: [16]),
  261. HashableSectionModel(model: "section 7", items: [17, 15, 4]),
  262. HashableSectionModel(model: "section 2", items: [2, 26, 23]),
  263. HashableSectionModel(model: "section 8", items: []),
  264. HashableSectionModel(model: "section 10", items: [8, 18, 13]),
  265. HashableSectionModel(model: "section 1", items: [28, 25, 6, 11, 10, 29, 24, 7, 19])
  266. ]
  267. */
  268. // Generates differential changes suitable for sectioned view consumption.
  269. // It will not only detect changes between two states, but it will also try to compress those changes into
  270. // almost minimal set of changes.
  271. //
  272. // I know, I know, it's ugly :( Totally agree, but this is the only general way I could find that works 100%, and
  273. // avoids UITableView quirks.
  274. //
  275. // Please take into consideration that I was also convinced about 20 times that I've found a simple general
  276. // solution, but then UITableView falls apart under stress testing :(
  277. //
  278. // Sincerely, if somebody else would present me this 250 lines of code, I would call him a mad man. I would think
  279. // that there has to be a simpler solution. Well, after 3 days, I'm not convinced any more :)
  280. //
  281. // Maybe it can be made somewhat simpler, but don't think it can be made much simpler.
  282. //
  283. // The algorithm could take anywhere from 1 to 3 table view transactions to finish the updates.
  284. //
  285. // * stage 1 - remove deleted sections and items
  286. // * stage 2 - move sections into place
  287. // * stage 3 - fix moved and new items
  288. //
  289. // There maybe exists a better division, but time will tell.
  290. //
  291. public static func differencesForSectionedView<Section: AnimatableSectionModelType>(
  292. initialSections: [Section],
  293. finalSections: [Section])
  294. throws -> [Changeset<Section>] {
  295. var result: [Changeset<Section>] = []
  296. var sectionCommands = try CommandGenerator<Section>.generatorForInitialSections(initialSections, finalSections: finalSections)
  297. result.append(contentsOf: try sectionCommands.generateDeleteSectionsDeletedItemsAndUpdatedItems())
  298. result.append(contentsOf: try sectionCommands.generateInsertAndMoveSections())
  299. result.append(contentsOf: try sectionCommands.generateInsertAndMovedItems())
  300. return result
  301. }
  302. private struct CommandGenerator<Section: AnimatableSectionModelType> {
  303. // swiftlint:disable:next nesting
  304. typealias Item = Section.Item
  305. let initialSections: [Section]
  306. let finalSections: [Section]
  307. let initialSectionData: ContiguousArray<SectionAssociatedData>
  308. let finalSectionData: ContiguousArray<SectionAssociatedData>
  309. let initialItemData: ContiguousArray<ContiguousArray<ItemAssociatedData>>
  310. let finalItemData: ContiguousArray<ContiguousArray<ItemAssociatedData>>
  311. let initialItemCache: ContiguousArray<ContiguousArray<Item>>
  312. let finalItemCache: ContiguousArray<ContiguousArray<Item>>
  313. static func generatorForInitialSections(
  314. _ initialSections: [Section],
  315. finalSections: [Section]
  316. ) throws -> CommandGenerator<Section> {
  317. let (initialSectionData, finalSectionData) = try calculateSectionMovements(initialSections: initialSections, finalSections: finalSections)
  318. let initialItemCache = ContiguousArray(initialSections.map {
  319. ContiguousArray($0.items)
  320. })
  321. let finalItemCache = ContiguousArray(finalSections.map {
  322. ContiguousArray($0.items)
  323. })
  324. let (initialItemData, finalItemData) = try calculateItemMovements(
  325. initialItemCache: initialItemCache,
  326. finalItemCache: finalItemCache,
  327. initialSectionData: initialSectionData,
  328. finalSectionData: finalSectionData
  329. )
  330. return CommandGenerator<Section>(
  331. initialSections: initialSections,
  332. finalSections: finalSections,
  333. initialSectionData: initialSectionData,
  334. finalSectionData: finalSectionData,
  335. initialItemData: initialItemData,
  336. finalItemData: finalItemData,
  337. initialItemCache: initialItemCache,
  338. finalItemCache: finalItemCache
  339. )
  340. }
  341. static func calculateItemMovements(
  342. initialItemCache: ContiguousArray<ContiguousArray<Item>>,
  343. finalItemCache: ContiguousArray<ContiguousArray<Item>>,
  344. initialSectionData: ContiguousArray<SectionAssociatedData>,
  345. finalSectionData: ContiguousArray<SectionAssociatedData>) throws
  346. -> (ContiguousArray<ContiguousArray<ItemAssociatedData>>, ContiguousArray<ContiguousArray<ItemAssociatedData>>) {
  347. var (initialItemData, finalItemData) = try Diff.calculateAssociatedData(
  348. initialItemCache: initialItemCache,
  349. finalItemCache: finalItemCache
  350. )
  351. let findNextUntouchedOldIndex = { (initialSectionIndex: Int, initialSearchIndex: Int?) -> Int? in
  352. guard var i2 = initialSearchIndex else {
  353. return nil
  354. }
  355. while i2 < initialSectionData[initialSectionIndex].itemCount {
  356. if initialItemData[initialSectionIndex][i2].event == .untouched {
  357. return i2
  358. }
  359. i2 += 1
  360. }
  361. return nil
  362. }
  363. // first mark deleted items
  364. for i in 0 ..< initialItemCache.count {
  365. guard initialSectionData[i].moveIndex != nil else {
  366. continue
  367. }
  368. var indexAfterDelete = 0
  369. for j in 0 ..< initialItemCache[i].count {
  370. guard let finalIndexPath = initialItemData[i][j].moveIndex else {
  371. initialItemData[i][j].event = .deleted
  372. continue
  373. }
  374. // from this point below, section has to be move type because it's initial and not deleted
  375. // because there is no move to inserted section
  376. if finalSectionData[finalIndexPath.sectionIndex].event == .inserted {
  377. initialItemData[i][j].event = .deleted
  378. continue
  379. }
  380. initialItemData[i][j].indexAfterDelete = indexAfterDelete
  381. indexAfterDelete += 1
  382. }
  383. }
  384. // mark moved or moved automatically
  385. for i in 0 ..< finalItemCache.count {
  386. guard let originalSectionIndex = finalSectionData[i].moveIndex else {
  387. continue
  388. }
  389. var untouchedIndex: Int? = 0
  390. for j in 0 ..< finalItemCache[i].count {
  391. untouchedIndex = findNextUntouchedOldIndex(originalSectionIndex, untouchedIndex)
  392. guard let originalIndex = finalItemData[i][j].moveIndex else {
  393. finalItemData[i][j].event = .inserted
  394. continue
  395. }
  396. // In case trying to move from deleted section, abort, otherwise it will crash table view
  397. if initialSectionData[originalIndex.sectionIndex].event == .deleted {
  398. finalItemData[i][j].event = .inserted
  399. continue
  400. }
  401. // original section can't be inserted
  402. else if initialSectionData[originalIndex.sectionIndex].event == .inserted {
  403. try precondition(false, "New section in initial sections, that is wrong")
  404. }
  405. let initialSectionEvent = initialSectionData[originalIndex.sectionIndex].event
  406. try precondition(initialSectionEvent == .moved || initialSectionEvent == .movedAutomatically, "Section not moved")
  407. let eventType = originalIndex == ItemPath(sectionIndex: originalSectionIndex, itemIndex: untouchedIndex ?? -1)
  408. ? EditEvent.movedAutomatically : EditEvent.moved
  409. initialItemData[originalIndex.sectionIndex][originalIndex.itemIndex].event = eventType
  410. finalItemData[i][j].event = eventType
  411. }
  412. }
  413. return (initialItemData, finalItemData)
  414. }
  415. static func calculateSectionMovements(initialSections: [Section], finalSections: [Section]) throws
  416. -> (ContiguousArray<SectionAssociatedData>, ContiguousArray<SectionAssociatedData>) {
  417. let initialSectionIndexes = try Diff.indexSections(initialSections)
  418. var initialSectionData = ContiguousArray<SectionAssociatedData>(repeating: SectionAssociatedData.initial, count: initialSections.count)
  419. var finalSectionData = ContiguousArray<SectionAssociatedData>(repeating: SectionAssociatedData.initial, count: finalSections.count)
  420. for (i, section) in finalSections.enumerated() {
  421. finalSectionData[i].itemCount = finalSections[i].items.count
  422. guard let initialSectionIndex = initialSectionIndexes[section.identity] else {
  423. continue
  424. }
  425. if initialSectionData[initialSectionIndex].moveIndex != nil {
  426. throw Error.duplicateSection(section: section)
  427. }
  428. initialSectionData[initialSectionIndex].moveIndex = i
  429. finalSectionData[i].moveIndex = initialSectionIndex
  430. }
  431. var sectionIndexAfterDelete = 0
  432. // deleted sections
  433. for i in 0 ..< initialSectionData.count {
  434. initialSectionData[i].itemCount = initialSections[i].items.count
  435. if initialSectionData[i].moveIndex == nil {
  436. initialSectionData[i].event = .deleted
  437. continue
  438. }
  439. initialSectionData[i].indexAfterDelete = sectionIndexAfterDelete
  440. sectionIndexAfterDelete += 1
  441. }
  442. // moved sections
  443. var untouchedOldIndex: Int? = 0
  444. let findNextUntouchedOldIndex = { (initialSearchIndex: Int?) -> Int? in
  445. guard var i = initialSearchIndex else {
  446. return nil
  447. }
  448. while i < initialSections.count {
  449. if initialSectionData[i].event == .untouched {
  450. return i
  451. }
  452. i += 1
  453. }
  454. return nil
  455. }
  456. // inserted and moved sections {
  457. // this should fix all sections and move them into correct places
  458. // 2nd stage
  459. for i in 0 ..< finalSections.count {
  460. untouchedOldIndex = findNextUntouchedOldIndex(untouchedOldIndex)
  461. // oh, it did exist
  462. if let oldSectionIndex = finalSectionData[i].moveIndex {
  463. let moveType = oldSectionIndex != untouchedOldIndex ? EditEvent.moved : EditEvent.movedAutomatically
  464. finalSectionData[i].event = moveType
  465. initialSectionData[oldSectionIndex].event = moveType
  466. }
  467. else {
  468. finalSectionData[i].event = .inserted
  469. }
  470. }
  471. // inserted sections
  472. for (i, section) in finalSectionData.enumerated() where section.moveIndex == nil {
  473. _ = finalSectionData[i].event == .inserted
  474. }
  475. return (initialSectionData, finalSectionData)
  476. }
  477. mutating func generateDeleteSectionsDeletedItemsAndUpdatedItems() throws -> [Changeset<Section>] {
  478. var deletedSections = [Int]()
  479. var deletedItems = [ItemPath]()
  480. var updatedItems = [ItemPath]()
  481. var afterDeleteState = [Section]()
  482. // mark deleted items {
  483. // 1rst stage again (I know, I know ...)
  484. for (i, initialItems) in initialItemCache.enumerated() {
  485. let event = initialSectionData[i].event
  486. // Deleted section will take care of deleting child items.
  487. // In case of moving an item from deleted section, tableview will
  488. // crash anyway, so this is not limiting anything.
  489. if event == .deleted {
  490. deletedSections.append(i)
  491. continue
  492. }
  493. var afterDeleteItems: [Section.Item] = []
  494. for j in 0 ..< initialItems.count {
  495. let event = initialItemData[i][j].event
  496. switch event {
  497. case .deleted:
  498. deletedItems.append(ItemPath(sectionIndex: i, itemIndex: j))
  499. case .moved, .movedAutomatically:
  500. let finalItemIndex = try initialItemData[i][j].moveIndex.unwrap()
  501. let finalItem = finalItemCache[finalItemIndex.sectionIndex][finalItemIndex.itemIndex]
  502. if finalItem != initialSections[i].items[j] {
  503. updatedItems.append(ItemPath(sectionIndex: i, itemIndex: j))
  504. afterDeleteItems.append(finalItem)
  505. } else {
  506. afterDeleteItems.append(initialSections[i].items[j])
  507. }
  508. default:
  509. try precondition(false, "Unhandled case")
  510. }
  511. }
  512. afterDeleteState.append(try Section(safeOriginal: initialSections[i], safeItems: afterDeleteItems))
  513. }
  514. // }
  515. if deletedItems.isEmpty && deletedSections.isEmpty && updatedItems.isEmpty {
  516. return []
  517. }
  518. return [Changeset(
  519. finalSections: afterDeleteState,
  520. deletedSections: deletedSections,
  521. deletedItems: deletedItems,
  522. updatedItems: updatedItems
  523. )]
  524. }
  525. func generateInsertAndMoveSections() throws -> [Changeset<Section>] {
  526. var movedSections = [(from: Int, to: Int)]()
  527. var insertedSections = [Int]()
  528. for i in 0 ..< initialSections.count {
  529. switch initialSectionData[i].event {
  530. case .deleted:
  531. break
  532. case .moved:
  533. movedSections.append((from: try initialSectionData[i].indexAfterDelete.unwrap(), to: try initialSectionData[i].moveIndex.unwrap()))
  534. case .movedAutomatically:
  535. break
  536. default:
  537. try precondition(false, "Unhandled case in initial sections")
  538. }
  539. }
  540. for i in 0 ..< finalSections.count {
  541. switch finalSectionData[i].event {
  542. case .inserted:
  543. insertedSections.append(i)
  544. default:
  545. break
  546. }
  547. }
  548. if insertedSections.isEmpty && movedSections.isEmpty {
  549. return []
  550. }
  551. // sections should be in place, but items should be original without deleted ones
  552. let sectionsAfterChange: [Section] = try self.finalSections.enumerated().map { i, s -> Section in
  553. let event = self.finalSectionData[i].event
  554. if event == .inserted {
  555. // it's already set up
  556. return s
  557. }
  558. else if event == .moved || event == .movedAutomatically {
  559. let originalSectionIndex = try finalSectionData[i].moveIndex.unwrap()
  560. let originalSection = initialSections[originalSectionIndex]
  561. var items: [Section.Item] = []
  562. items.reserveCapacity(originalSection.items.count)
  563. let itemAssociatedData = self.initialItemData[originalSectionIndex]
  564. for j in 0 ..< originalSection.items.count {
  565. let initialData = itemAssociatedData[j]
  566. guard initialData.event != .deleted else {
  567. continue
  568. }
  569. guard let finalIndex = initialData.moveIndex else {
  570. try precondition(false, "Item was moved, but no final location.")
  571. continue
  572. }
  573. items.append(finalItemCache[finalIndex.sectionIndex][finalIndex.itemIndex])
  574. }
  575. let modifiedSection = try Section(safeOriginal: s, safeItems: items)
  576. return modifiedSection
  577. }
  578. else {
  579. try precondition(false, "This is weird, this shouldn't happen")
  580. return s
  581. }
  582. }
  583. return [Changeset(
  584. finalSections: sectionsAfterChange,
  585. insertedSections: insertedSections,
  586. movedSections: movedSections
  587. )]
  588. }
  589. mutating func generateInsertAndMovedItems() throws -> [Changeset<Section>] {
  590. var insertedItems = [ItemPath]()
  591. var movedItems = [(from: ItemPath, to: ItemPath)]()
  592. // mark new and moved items {
  593. // 3rd stage
  594. for i in 0 ..< finalSections.count {
  595. let finalSection = finalSections[i]
  596. let sectionEvent = finalSectionData[i].event
  597. // new and deleted sections cause reload automatically
  598. if sectionEvent != .moved && sectionEvent != .movedAutomatically {
  599. continue
  600. }
  601. for j in 0 ..< finalSection.items.count {
  602. let currentItemEvent = finalItemData[i][j].event
  603. try precondition(currentItemEvent != .untouched, "Current event is not untouched")
  604. let event = finalItemData[i][j].event
  605. switch event {
  606. case .inserted:
  607. insertedItems.append(ItemPath(sectionIndex: i, itemIndex: j))
  608. case .moved:
  609. let originalIndex = try finalItemData[i][j].moveIndex.unwrap()
  610. let finalSectionIndex = try initialSectionData[originalIndex.sectionIndex].moveIndex.unwrap()
  611. let moveFromItemWithIndex = try initialItemData[originalIndex.sectionIndex][originalIndex.itemIndex].indexAfterDelete.unwrap()
  612. let moveCommand = (
  613. from: ItemPath(sectionIndex: finalSectionIndex, itemIndex: moveFromItemWithIndex),
  614. to: ItemPath(sectionIndex: i, itemIndex: j)
  615. )
  616. movedItems.append(moveCommand)
  617. default:
  618. break
  619. }
  620. }
  621. }
  622. // }
  623. if insertedItems.isEmpty && movedItems.isEmpty {
  624. return []
  625. }
  626. return [Changeset(
  627. finalSections: finalSections,
  628. insertedItems: insertedItems,
  629. movedItems: movedItems
  630. )]
  631. }
  632. }
  633. }