JCChatViewUpdate.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. //
  2. // JCChatViewUpdate.swift
  3. // JChat
  4. //
  5. // Created by deng on 10/04/2017.
  6. // Copyright © 2017 HXHG. All rights reserved.
  7. //
  8. import UIKit
  9. internal enum JCChatViewUpdateChangeItem {
  10. case insert(JCMessageType, at: Int)
  11. case update(JCMessageType, at: Int)
  12. case remove(at: Int)
  13. case move(at: Int, to: Int)
  14. var at: Int {
  15. switch self {
  16. case .insert(_, let at): return at
  17. case .update(_, let at): return at
  18. case .remove(let at): return at
  19. case .move(let at, _): return at
  20. }
  21. }
  22. }
  23. internal enum JCChatViewUpdateChange: CustomStringConvertible {
  24. case move(from: Int, to: Int)
  25. case update(from: Int, to: Int)
  26. case insert(from: Int, to: Int)
  27. case remove(from: Int, to: Int)
  28. var from: Int {
  29. switch self {
  30. case .move(let from, _): return from
  31. case .insert(let from, _): return from
  32. case .update(let from, _): return from
  33. case .remove(let from, _): return from
  34. }
  35. }
  36. var to: Int {
  37. switch self {
  38. case .move(_, let to): return to
  39. case .insert(_, let to): return to
  40. case .update(_, let to): return to
  41. case .remove(_, let to): return to
  42. }
  43. }
  44. var isMove: Bool {
  45. switch self {
  46. case .move: return true
  47. default: return false
  48. }
  49. }
  50. var isUpdate: Bool {
  51. switch self {
  52. case .update: return true
  53. default: return false
  54. }
  55. }
  56. var isRemove: Bool {
  57. switch self {
  58. case .remove: return true
  59. default: return false
  60. }
  61. }
  62. var isInsert: Bool {
  63. switch self {
  64. case .insert: return true
  65. default: return false
  66. }
  67. }
  68. var description: String {
  69. let from = self.from >= 0 ? "\(self.from)" : "N"
  70. let to = self.to >= 0 ? "\(self.to)" : "N"
  71. switch self {
  72. case .move: return "M\(from)/\(to)"
  73. case .insert: return "A\(from)/\(to)"
  74. case .update: return "R\(from)/\(to)"
  75. case .remove: return "D\(from)/\(to)"
  76. }
  77. }
  78. func offset(_ offset: Int) -> JCChatViewUpdateChange {
  79. let from = self.from + offset + max(min(self.from, 0), -1) * offset
  80. let to = self.to + offset + max(min(self.to, 0), -1) * offset
  81. // convert
  82. switch self {
  83. case .move: return .move(from: from, to: to)
  84. case .insert: return .insert(from: from, to: to)
  85. case .update: return .update(from: from, to: to)
  86. case .remove: return .remove(from: from, to: to)
  87. }
  88. }
  89. }
  90. internal class JCChatViewUpdate: NSObject {
  91. internal init(newData: JCChatViewData, oldData: JCChatViewData, updateItems: Array<JCChatViewUpdateChangeItem>) {
  92. self.newData = newData
  93. self.oldData = oldData
  94. self.updateItems = updateItems
  95. super.init()
  96. self.updateChanges = _computeItemUpdates(newData, oldData, updateItems)
  97. }
  98. // MARK: compute
  99. internal func _computeItemUpdates(_ newData: JCChatViewData, _ oldData: JCChatViewData, _ updateItems: Array<JCChatViewUpdateChangeItem>) -> Array<JCChatViewUpdateChange> {
  100. guard !updateItems.isEmpty else {
  101. return []
  102. }
  103. var allInserts: Array<(Int, JCMessageType)> = []
  104. var allUpdates: Array<(Int, JCMessageType)> = []
  105. var allRemoves: Array<(Int)> = []
  106. var allMoves: Array<(Int, Int)> = []
  107. // get max & min
  108. let (first, last) = updateItems.reduce((.max, .min)) { result, item -> (Int, Int) in
  109. switch item {
  110. case .move(let from, let to):
  111. // ignore for source equ dest
  112. guard abs(from - to) >= 1 else {
  113. return result
  114. }
  115. // move message
  116. allMoves.append((from, to))
  117. // splite to insert & remove
  118. if let message = _element(at: from) {
  119. allRemoves.append((from))
  120. allInserts.append((to + 1, message))
  121. }
  122. // from + 1: the selected row will change
  123. return (min(min(from, to + 1), result.0), max(max(from + 1, to + 1), result.1))
  124. case .remove(let index):
  125. // remove message
  126. allRemoves.append((index))
  127. return (min(index, result.0), max(index + 1, result.1))
  128. case .update(let message, let index):
  129. // update message
  130. allUpdates.append((index, message))
  131. return (min(index, result.0), max(index + 1, result.1))
  132. case .insert(let message, let index):
  133. // insert message
  134. allInserts.append((index, message))
  135. return (min(index, result.0), max(index, result.1))
  136. }
  137. }
  138. // is empty
  139. guard first != .max && last != .min else {
  140. return []
  141. }
  142. // sort
  143. // allInserts.sort { $0.0 < $1.0 }
  144. // allUpdates.sort { $0.0 < $1.0 }
  145. // allRemoves.sort { $0 < $1 }
  146. // allMoves.sort { $0.0 < $1.0 }
  147. let count = oldData.count
  148. let begin = first - 1 // prev
  149. let end = last + 1 // next
  150. var ii = allInserts.startIndex
  151. var iu = allUpdates.startIndex
  152. var ir = allRemoves.startIndex
  153. // var im = allMoves.startIndex
  154. // priority: insert > remove > update > move
  155. var items: Array<JCMessageType> = []
  156. // processing
  157. (first ... last).forEach { index in
  158. // do you need to insert the operation?
  159. while ii < allInserts.endIndex && allInserts[ii].0 == index {
  160. items.append(allInserts[ii].1)
  161. ii += 1
  162. }
  163. // do you need to do this?
  164. guard index < last && index < count else {
  165. return
  166. }
  167. // do you need to remove the operation?
  168. while ir < allRemoves.endIndex && allRemoves[ir] == index {
  169. // adjust previous tl-message & next tl-message, if needed
  170. if let content = _element(at: index - 1)?.content as? JCMessageTimeLineContent {
  171. content.after = nil
  172. }
  173. if let content = _element(at: index + 1)?.content as? JCMessageTimeLineContent {
  174. content.before = nil
  175. }
  176. // move to next operator(prevent repeat operation)
  177. while ir < allRemoves.endIndex && allRemoves[ir] == index {
  178. ir += 1
  179. }
  180. // can't update or copy
  181. return
  182. }
  183. // do you need to update the operation?
  184. while iu < allUpdates.endIndex && allUpdates[iu].0 == index {
  185. let message = allUpdates[iu].1
  186. // updating
  187. items.append(message)
  188. // adjust previous tl-message & next tl-message, if needed
  189. if let content = _element(at: index - 1)?.content as? JCMessageTimeLineContent {
  190. content.after = message
  191. }
  192. if let content = _element(at: index + 1)?.content as? JCMessageTimeLineContent {
  193. content.before = message
  194. }
  195. // move to next operator(prevent repeat operation)
  196. while iu < allUpdates.endIndex && allUpdates[iu].0 == index {
  197. iu += 1
  198. }
  199. // can't copy
  200. return
  201. }
  202. // copy
  203. items.append(oldData[index])
  204. }
  205. // convert messages and replace specify message
  206. let newItems = items as [JCMessageType]
  207. let convertedItems = _convert(messages: newItems, first: _element(at: begin), last: _element(at: end - 1))
  208. let selectedRange = max(begin, 0) ..< min(end, count)
  209. let selectedItems = oldData.subarray(with: selectedRange)
  210. // compute index paths
  211. let start = selectedRange.lowerBound
  212. // lcs
  213. let diff = _diff(selectedItems, convertedItems).map { $0.offset(start) }
  214. // ::
  215. // replace
  216. newData.elements = oldData.elements
  217. newData.replaceSubrange(selectedRange, with: convertedItems)
  218. return diff
  219. }
  220. // MARK: convert message
  221. private func _convert(messages elements: [JCMessageType], first: JCMessageType?, last: JCMessageType?) -> [JCMessageType] {
  222. // merge
  223. let elements = [first].flatMap({ $0 }) + elements + [last].flatMap({ $0 })
  224. // processing
  225. return (0 ..< elements.count).reduce(NSMutableArray(capacity: elements.count * 2)) { result, index in
  226. let current = elements[index]
  227. result.add(current)
  228. // continue
  229. return result
  230. } as! [JCMessageType]
  231. }
  232. internal func _element(at index: Int) -> JCMessageType? {
  233. guard index >= 0 && index < oldData.count else {
  234. return nil
  235. }
  236. return oldData[index]
  237. }
  238. // MARK: compare
  239. private func _equal<T: JCMessageType>(_ lhs: T, _ rhs: T) -> Bool {
  240. return lhs.identifier == rhs.identifier && lhs.options.state == rhs.options.state
  241. }
  242. private func _diff<T: JCMessageType>(_ src: Array<T>, _ dest: Array<T>) -> Array<JCChatViewUpdateChange> {
  243. let len1 = src.count
  244. let len2 = dest.count
  245. var c = [[Int]](repeating: [Int](repeating: 0, count: len2 + 1), count: len1 + 1)
  246. // lcs + 动态规划
  247. for i in 1 ..< len1 + 1 {
  248. for j in 1 ..< len2 + 1 {
  249. if _equal(src[i - 1], (dest[j - 1])) {
  250. c[i][j] = c[i - 1][j - 1] + 1
  251. } else {
  252. c[i][j] = max(c[i - 1][j], c[i][j - 1])
  253. }
  254. }
  255. }
  256. var i = len1
  257. var j = len2
  258. var rms: Array<(from: Int, to: Int)> = []
  259. var adds: Array<(from: Int, to: Int)> = []
  260. // create the optimal path
  261. repeat {
  262. guard i != 0 else {
  263. // the remaining is add
  264. while j > 0 {
  265. adds.append((from: i - 1, to: j - 1))
  266. j -= 1
  267. }
  268. break
  269. }
  270. guard j != 0 else {
  271. // the remaining is remove
  272. while i > 0 {
  273. rms.append((from: i - 1, to: j - 1))
  274. i -= 1
  275. }
  276. break
  277. }
  278. guard !_equal(src[i - 1], (dest[j - 1])) else {
  279. // no change, ignore
  280. i -= 1
  281. j -= 1
  282. continue
  283. }
  284. // check the weight
  285. if c[i - 1][j] > c[i][j - 1] {
  286. // is remove
  287. rms.append((from: i - 1, to: j - 1))
  288. i -= 1
  289. } else {
  290. // is add
  291. adds.append((from: i - 1, to: j - 1))
  292. j -= 1
  293. }
  294. } while i > 0 || j > 0
  295. var results: Array<JCChatViewUpdateChange> = []
  296. results.reserveCapacity(rms.count + adds.count)
  297. // move(f,t): f = remove(f), t = insert(t), new move(f,t): f = remove(f), t = insert(f)
  298. // update(f,t): f = remove(f), t = insert(t), new update(f,t): f = remove(f), t = insert(f)
  299. // automatic merge delete and update items
  300. results.append(contentsOf: rms.map({ item in
  301. let from = item.from
  302. let delElement = src[from]
  303. // can't merge to move item?
  304. if let addIndex = adds.index(where: { _equal(dest[$0.to], delElement) }) {
  305. let addItem = adds.remove(at: addIndex)
  306. return .move(from: from, to: addItem.to)
  307. }
  308. // can't merge to update item?
  309. if let addIndex = adds.index(where: { $0.to == from }) {
  310. let addItem = adds[addIndex]
  311. let addElement = dest[addItem.to]
  312. // the same type is allowed to merge
  313. if type(of: delElement.content) == type(of: addElement.content) {
  314. adds.remove(at: addIndex)
  315. return .update(from: from, to: addItem.to)
  316. }
  317. }
  318. return .remove(from: item.from, to: -1)
  319. }))
  320. // automatic merge insert items
  321. results.append(contentsOf: adds.map({ item in
  322. return .insert(from: -1, to: item.to)
  323. }))
  324. // sort
  325. return results.sorted { $0.from < $1.from }
  326. }
  327. // MARK: property
  328. internal let newData: JCChatViewData
  329. internal let oldData: JCChatViewData
  330. internal let updateItems: Array<JCChatViewUpdateChangeItem>
  331. internal var updateChanges: Array<JCChatViewUpdateChange>?
  332. internal static var minimuxTimeInterval: TimeInterval = 60
  333. }