ThreadSafeArray.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. //
  2. // ThreadSafeArray.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 31/01/24.
  6. // Copyright © 2024 Marino Faggiana. All rights reserved.
  7. //
  8. // http://basememara.com/creating-thread-safe-arrays-in-swift/
  9. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  10. //
  11. // This program is free software: you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published by
  13. // the Free Software Foundation, either version 3 of the License, or
  14. // (at your option) any later version.
  15. //
  16. // This program is distributed in the hope that it will be useful,
  17. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. // GNU General Public License for more details.
  20. //
  21. // You should have received a copy of the GNU General Public License
  22. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. //
  24. import Foundation
  25. import UIKit
  26. /// A thread-safe array.
  27. public class ThreadSafeArray<Element> {
  28. private var array = [Element]()
  29. private let queue = DispatchQueue(label: "com.nextcloud.ThreadSafeArray", attributes: .concurrent)
  30. public init() { }
  31. public convenience init(_ array: [Element]) {
  32. self.init()
  33. self.array = array
  34. }
  35. }
  36. // MARK: - Properties
  37. public extension ThreadSafeArray {
  38. /// The first element of the collection.
  39. var first: Element? {
  40. var result: Element?
  41. queue.sync { result = self.array.first }
  42. return result
  43. }
  44. /// The last element of the collection.
  45. var last: Element? {
  46. var result: Element?
  47. queue.sync { result = self.array.last }
  48. return result
  49. }
  50. /// The number of elements in the array.
  51. var count: Int {
  52. var result = 0
  53. queue.sync { result = self.array.count }
  54. return result
  55. }
  56. /// A Boolean value indicating whether the collection is empty.
  57. var isEmpty: Bool {
  58. var result = false
  59. queue.sync { result = self.array.isEmpty }
  60. return result
  61. }
  62. /// A textual representation of the array and its elements.
  63. var description: String {
  64. var result = ""
  65. queue.sync { result = self.array.description }
  66. return result
  67. }
  68. }
  69. // MARK: - Immutable
  70. public extension ThreadSafeArray {
  71. /// Returns the first element of the sequence that satisfies the given predicate.
  72. ///
  73. /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
  74. /// - Returns: The first element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate.
  75. func first(where predicate: (Element) -> Bool) -> Element? {
  76. var result: Element?
  77. queue.sync { result = self.array.first(where: predicate) }
  78. return result
  79. }
  80. /// Returns the last element of the sequence that satisfies the given predicate.
  81. ///
  82. /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
  83. /// - Returns: The last element of the sequence that satisfies predicate, or nil if there is no element that satisfies predicate.
  84. func last(where predicate: (Element) -> Bool) -> Element? {
  85. var result: Element?
  86. queue.sync { result = self.array.last(where: predicate) }
  87. return result
  88. }
  89. /// Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
  90. ///
  91. /// - Parameter isIncluded: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.
  92. /// - Returns: An array of the elements that includeElement allowed.
  93. func filter(_ isIncluded: @escaping (Element) -> Bool) -> ThreadSafeArray {
  94. var result: ThreadSafeArray?
  95. queue.sync { result = ThreadSafeArray(self.array.filter(isIncluded)) }
  96. return result!
  97. }
  98. /// Returns the first index in which an element of the collection satisfies the given predicate.
  99. ///
  100. /// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
  101. /// - Returns: The index of the first element for which predicate returns true. If no elements in the collection satisfy the given predicate, returns nil.
  102. func index(where predicate: (Element) -> Bool) -> Int? {
  103. var result: Int?
  104. queue.sync { result = self.array.firstIndex(where: predicate) }
  105. return result
  106. }
  107. /// Returns the elements of the collection, sorted using the given predicate as the comparison between elements.
  108. ///
  109. /// - Parameter areInIncreasingOrder: A predicate that returns true if its first argument should be ordered before its second argument; otherwise, false.
  110. /// - Returns: A sorted array of the collection’s elements.
  111. func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> ThreadSafeArray {
  112. var result: ThreadSafeArray?
  113. queue.sync { result = ThreadSafeArray(self.array.sorted(by: areInIncreasingOrder)) }
  114. return result!
  115. }
  116. /// Returns an array containing the results of mapping the given closure over the sequence’s elements.
  117. ///
  118. /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
  119. /// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
  120. func map<ElementOfResult>(_ transform: @escaping (Element) -> ElementOfResult) -> [ElementOfResult] {
  121. var result = [ElementOfResult]()
  122. queue.sync { result = self.array.map(transform) }
  123. return result
  124. }
  125. /// Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.
  126. ///
  127. /// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
  128. /// - Returns: An array of the non-nil results of calling transform with each element of the sequence.
  129. func compactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
  130. var result = [ElementOfResult]()
  131. queue.sync { result = self.array.compactMap(transform) }
  132. return result
  133. }
  134. /// Returns the result of combining the elements of the sequence using the given closure.
  135. ///
  136. /// - Parameters:
  137. /// - initialResult: The value to use as the initial accumulating value. initialResult is passed to nextPartialResult the first time the closure is executed.
  138. /// - nextPartialResult: A closure that combines an accumulating value and an element of the sequence into a new accumulating value, to be used in the next call of the nextPartialResult closure or returned to the caller.
  139. /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult.
  140. func reduce<ElementOfResult>(_ initialResult: ElementOfResult, _ nextPartialResult: @escaping (ElementOfResult, Element) -> ElementOfResult) -> ElementOfResult {
  141. var result: ElementOfResult?
  142. queue.sync { result = self.array.reduce(initialResult, nextPartialResult) }
  143. return result ?? initialResult
  144. }
  145. /// Returns the result of combining the elements of the sequence using the given closure.
  146. ///
  147. /// - Parameters:
  148. /// - initialResult: The value to use as the initial accumulating value.
  149. /// - updateAccumulatingResult: A closure that updates the accumulating value with an element of the sequence.
  150. /// - Returns: The final accumulated value. If the sequence has no elements, the result is initialResult.
  151. func reduce<ElementOfResult>(into initialResult: ElementOfResult, _ updateAccumulatingResult: @escaping (inout ElementOfResult, Element) -> Void) -> ElementOfResult {
  152. var result: ElementOfResult?
  153. queue.sync { result = self.array.reduce(into: initialResult, updateAccumulatingResult) }
  154. return result ?? initialResult
  155. }
  156. /// Calls the given closure on each element in the sequence in the same order as a for-in loop.
  157. ///
  158. /// - Parameter body: A closure that takes an element of the sequence as a parameter.
  159. func forEach(_ body: (Element) -> Void) {
  160. queue.sync { self.array.forEach(body) }
  161. }
  162. /// Returns a Boolean value indicating whether the sequence contains an element that satisfies the given predicate.
  163. ///
  164. /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element represents a match.
  165. /// - Returns: true if the sequence contains an element that satisfies predicate; otherwise, false.
  166. func contains(where predicate: (Element) -> Bool) -> Bool {
  167. var result = false
  168. queue.sync { result = self.array.contains(where: predicate) }
  169. return result
  170. }
  171. /// Returns a Boolean value indicating whether every element of a sequence satisfies a given predicate.
  172. ///
  173. /// - Parameter predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value that indicates whether the passed element satisfies a condition.
  174. /// - Returns: true if the sequence contains only elements that satisfy predicate; otherwise, false.
  175. func allSatisfy(_ predicate: (Element) -> Bool) -> Bool {
  176. var result = false
  177. queue.sync { result = self.array.allSatisfy(predicate) }
  178. return result
  179. }
  180. /// Returns the array
  181. ///
  182. /// - Returns: the array part.
  183. func getArray() -> [Element]? {
  184. var results: [Element]?
  185. queue.sync { results = self.array }
  186. return results
  187. }
  188. }
  189. // MARK: - Mutable
  190. public extension ThreadSafeArray {
  191. /// Adds a new element at the end of the array.
  192. ///
  193. /// - Parameter element: The element to append to the array.
  194. func append(_ element: Element) {
  195. queue.async(flags: .barrier) {
  196. self.array.append(element)
  197. }
  198. }
  199. /// Adds new elements at the end of the array.
  200. ///
  201. /// - Parameter element: The elements to append to the array.
  202. func append(_ elements: [Element]) {
  203. queue.async(flags: .barrier) {
  204. self.array += elements
  205. }
  206. }
  207. /// Inserts a new element at the specified position.
  208. ///
  209. /// - Parameters:
  210. /// - element: The new element to insert into the array.
  211. /// - index: The position at which to insert the new element.
  212. func insert(_ element: Element, at index: Int) {
  213. queue.async(flags: .barrier) {
  214. self.array.insert(element, at: index)
  215. }
  216. }
  217. /// Removes and returns the element at the specified position.
  218. ///
  219. /// - Parameters:
  220. /// - index: The position of the element to remove.
  221. /// - completion: The handler with the removed element.
  222. func remove(at index: Int, completion: ((Element) -> Void)? = nil) {
  223. queue.async(flags: .barrier) {
  224. let element = self.array.remove(at: index)
  225. DispatchQueue.main.async { completion?(element) }
  226. }
  227. }
  228. /// Removes and returns the elements that meet the criteria.
  229. ///
  230. /// - Parameters:
  231. /// - predicate: A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element is a match.
  232. /// - completion: The handler with the removed elements.
  233. func remove(where predicate: @escaping (Element) -> Bool, completion: (([Element]) -> Void)? = nil) {
  234. queue.async(flags: .barrier) {
  235. var elements = [Element]()
  236. while let index = self.array.firstIndex(where: predicate) {
  237. elements.append(self.array.remove(at: index))
  238. }
  239. DispatchQueue.main.async { completion?(elements) }
  240. }
  241. }
  242. /// Removes all elements from the array.
  243. ///
  244. /// - Parameter completion: The handler with the removed elements.
  245. func removeAll(completion: (([Element]) -> Void)? = nil) {
  246. queue.async(flags: .barrier) {
  247. let elements = self.array
  248. self.array.removeAll()
  249. DispatchQueue.main.async { completion?(elements) }
  250. }
  251. }
  252. }
  253. public extension ThreadSafeArray {
  254. /// Accesses the element at the specified position if it exists.
  255. ///
  256. /// - Parameter index: The position of the element to access.
  257. /// - Returns: optional element if it exists.
  258. subscript(index: Int) -> Element? {
  259. get {
  260. var result: Element?
  261. queue.sync {
  262. guard self.array.startIndex..<self.array.endIndex ~= index else { return }
  263. result = self.array[index]
  264. }
  265. return result
  266. }
  267. set {
  268. guard let newValue = newValue else { return }
  269. queue.async(flags: .barrier) {
  270. self.array[index] = newValue
  271. }
  272. }
  273. }
  274. }
  275. // MARK: - Equatable
  276. public extension ThreadSafeArray where Element: Equatable {
  277. /// Returns a Boolean value indicating whether the sequence contains the given element.
  278. ///
  279. /// - Parameter element: The element to find in the sequence.
  280. /// - Returns: true if the element was found in the sequence; otherwise, false.
  281. func contains(_ element: Element) -> Bool {
  282. var result = false
  283. queue.sync { result = self.array.contains(element) }
  284. return result
  285. }
  286. }
  287. // MARK: - Infix operators
  288. public extension ThreadSafeArray {
  289. /// Adds a new element at the end of the array.
  290. ///
  291. /// - Parameters:
  292. /// - left: The collection to append to.
  293. /// - right: The element to append to the array.
  294. static func += (left: inout ThreadSafeArray, right: Element) {
  295. left.append(right)
  296. }
  297. /// Adds new elements at the end of the array.
  298. ///
  299. /// - Parameters:
  300. /// - left: The collection to append to.
  301. /// - right: The elements to append to the array.
  302. static func += (left: inout ThreadSafeArray, right: [Element]) {
  303. left.append(right)
  304. }
  305. }