Diff.swift 32 KB

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