ThreadSafeArray.swift 13 KB

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