         let toAddIndexPaths = toAdd.map { IndexPath(row: $0.offset, section: section) }
         insertRows(at: toAddIndexPaths, with: animation)
     }
+
+    private final func moveAndReload<Row: Equatable>(from oldRows: [Row], to newRows: [Row], withPreviousResults results: [Row], in section: Int, with animation: RowAnimation, rowNeedsReload: (Row, Row) -> Bool) {
+        var indexPathsToReload: [IndexPath] = []
+        for (idx, row) in newRows.enumerated() where oldRows.contains(row) {
+            guard let oldIdx = results.firstIndex(of: row) else { continue }
+            let newIndexPath = IndexPath(row: idx, section: section)
+            if oldIdx != idx {
+                moveRow(at: IndexPath(row: oldIdx, section: section), to: newIndexPath)
+            }
+            if rowNeedsReload(results[oldIdx], row) {
+                indexPathsToReload.append(newIndexPath)
+            }
+        }
+        if !indexPathsToReload.isEmpty {
+            reloadRows(at: indexPathsToReload, with: animation)
+        }
+    }
+
+    private final func moveAndReload<Row: UITableViewReloadableObject>(from oldRows: [Row], to newRows: [Row], withPreviousResults results: [Row], in section: Int, with animation: RowAnimation) {
+        moveAndReload(from: oldRows, to: newRows, withPreviousResults: results, in: section, with: animation, rowNeedsReload: { $1.needsReload(from: $0) })
+    }
     
     private final func move<Row: Equatable>(from oldRows: [Row], to newRows: [Row], withPreviousResults results: [Row], in section: Int, with animation: RowAnimation) {
-        for (idx, row) in newRows.enumerated() where oldRows.contains(row) {
-            guard let oldIdx = results.firstIndex(of: row), oldIdx != idx else { continue }
-            let oldIndexPath = IndexPath(row: oldIdx, section: section)
-            let newIndexPath = IndexPath(row: idx, section: section)
-            moveRow(at: oldIndexPath, to: newIndexPath)
-        }
+        moveAndReload(from: oldRows, to: newRows, withPreviousResults: results, in: section, with: animation, rowNeedsReload: { _, _ in false })
     }
 }

}

extension UITableView {
    private final func _performBatchUpdates(_ updates: @escaping () -> (), completion: ((Bool) -> ())? = nil) {
        if #available(
            iOS 11, iOSApplicationExtension 11,
            tvOS 11, tvOSApplicationExtension 11,

        }
    }

    public func update<Section: UITableViewSectionObject>(from oldSections: [Section] = [], to newSections: [Section], animated: Bool = true, completion: ((Bool) -> ())? = nil)
        where Section.Row: UITableViewReloadableObject
    {
        update(from: oldSections, to: newSections, animated: animated, movingAndReloadingRowsWith: moveAndReload, completion: completion)
    }

    public func update<Section: UITableViewSectionObject>(from oldSections: [Section] = [], to newSections: [Section], animated: Bool = true, completion: ((Bool) -> ())? = nil) {
        update(from: oldSections, to: newSections, animated: animated, movingAndReloadingRowsWith: move, completion: completion)
    }

    public func update<Row: UITableViewReloadableObject>(from oldRows: [Row] = [], to newRows: [Row], in section: Int, animated: Bool = true, completion: ((Bool) -> ())? = nil) {
        update(from: oldRows, to: newRows, in: section, animated: animated, movingAndReloadingWith: { results, animation in
            self.moveAndReload(from: oldRows, to: newRows, withPreviousResults: results, in: section, with: animation)
        }, completion: completion)
    }
    
    public func update<Row: Equatable>(from oldRows: [Row] = [], to newRows: [Row], in section: Int, animated: Bool = true, completion: ((Bool) -> ())? = nil) {
        update(from: oldRows, to: newRows, in: section, animated: animated, movingAndReloadingWith: { results, animation in
            self.move(from: oldRows, to: newRows, withPreviousResults: results, in: section, with: animation)
        }, completion: completion)
    }
    
    // MARK: - Helpers
    private func update<Section: UITableViewSectionObject>(from oldSections: [Section], to newSections: [Section], animated: Bool,
                                                           movingAndReloadingRowsWith moveAndReloadRows: @escaping ([Section.Row], [Section.Row], [Section.Row], Int, RowAnimation) -> (),
                                                           completion: ((Bool) -> ())?) {
        let animation: RowAnimation = animated ? .automatic : .none

        guard !oldSections.isEmpty else {
            return _performBatchUpdates({
                insertSections(IndexSet(newSections.indices), with: animation)
            }, completion: completion)
        }

        var sectionResults = oldSections
        var rowResults = [Optional<Array<Section.Row>>]()
        _performBatchUpdates({
            // Remove sections
            let toRemove = oldSections.enumerated().filter { !newSections.contains($1) }
            sectionResults.removeAll { section in toRemove.contains { $0.element == section } }
            deleteSections(IndexSet(toRemove.map { $0.offset }), with: animation)

            // Add sections
            var toAddSections = IndexSet()
