WeakSynchronizedSet.swift 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. //
  2. // WeakSynchronizedSet.swift
  3. // LoopKit
  4. //
  5. // Created by Michael Pangburn
  6. // Copyright © 2019 LoopKit Authors. All rights reserved.
  7. //
  8. import Foundation
  9. /// A set-like collection of weak types, providing closure-based iteration on a client-specified queue
  10. /// Mutations and iterations are thread-safe
  11. public class WeakSynchronizedSet<Element> {
  12. private typealias Identifier = ObjectIdentifier
  13. private typealias ElementContainer = ElementDispatchContainer<Element>
  14. private class ElementDispatchContainer<Element> {
  15. private weak var _element: AnyObject?
  16. weak var queue: DispatchQueue?
  17. var element: Element? {
  18. return _element as? Element
  19. }
  20. init(element: Element, queue: DispatchQueue) {
  21. // All Swift values are implicitly convertible to `AnyObject`,
  22. // so this runtime check is the tradeoff for supporting class-constrained protocol types.
  23. precondition(Mirror(reflecting: element).displayStyle == .class, "Weak references can only be held of class types.")
  24. self._element = element as AnyObject
  25. self.queue = queue
  26. }
  27. func call(_ body: @escaping (_ element: Element) -> Void) {
  28. guard let queue = self.queue, let element = self.element else {
  29. return
  30. }
  31. queue.async {
  32. body(element)
  33. }
  34. }
  35. }
  36. private let elements: Locked<[Identifier: ElementContainer]>
  37. public init() {
  38. elements = Locked([:])
  39. }
  40. /// Adds an element and its calling queue
  41. ///
  42. /// - Parameters:
  43. /// - element: The element
  44. /// - queue: The queue to use when performing calls with the element
  45. public func insert(_ element: Element, queue: DispatchQueue) {
  46. insert(ElementDispatchContainer(element: element, queue: queue))
  47. }
  48. /// Prunes any element references that have been deallocated
  49. /// - Returns: A reference to the instance for easy chaining
  50. @discardableResult public func cleanupDeallocatedElements() -> Self {
  51. elements.mutate { (storage) in
  52. storage = storage.compactMapValues { $0.element == nil ? nil : $0 }
  53. }
  54. return self
  55. }
  56. /// Whether the element is in the set
  57. ///
  58. /// - Parameter element: The element
  59. /// - Returns: True if the element is in the set
  60. public func contains(_ element: Element) -> Bool {
  61. let id = identifier(for: element)
  62. return elements.value[id] != nil
  63. }
  64. /// The total number of element in the set
  65. ///
  66. /// Deallocated references are counted, so calling `cleanupDeallocatedElements` is advised to maintain accuracy of this value
  67. public var count: Int {
  68. return elements.value.count
  69. }
  70. /// Calls the given closure on each element in the set, on the queue specified when the element was added
  71. ///
  72. /// The order of calls is not defined
  73. ///
  74. /// - Parameter body: The closure to execute
  75. public func forEach(_ body: @escaping (Element) -> Void) {
  76. // Hold the lock while we iterate, since each call is dispatched out
  77. elements.mutate { (elements) in
  78. elements.forEach { (pair) in
  79. pair.value.call(body)
  80. }
  81. }
  82. }
  83. /// Removes the specified element from the set
  84. ///
  85. /// - Parameter element: The element
  86. public func removeElement(_ element: Element) {
  87. removeValue(forKey: identifier(for: element))
  88. }
  89. }
  90. extension WeakSynchronizedSet {
  91. private func identifier(for element: Element) -> ObjectIdentifier {
  92. return ObjectIdentifier(element as AnyObject)
  93. }
  94. private func identifier(for elementContainer: ElementContainer) -> ObjectIdentifier? {
  95. guard let element = elementContainer.element else {
  96. return nil
  97. }
  98. return identifier(for: element)
  99. }
  100. @discardableResult
  101. private func insert(_ newMember: ElementContainer) -> (inserted: Bool, memberAfterInsert: ElementContainer?) {
  102. guard let id = identifier(for: newMember) else {
  103. return (inserted: false, memberAfterInsert: nil)
  104. }
  105. var result: (inserted: Bool, memberAfterInsert: ElementContainer?)!
  106. elements.mutate { (storage) in
  107. if let existingMember = storage[id] {
  108. result = (inserted: false, memberAfterInsert: existingMember)
  109. } else {
  110. storage[id] = newMember
  111. result = (inserted: true, memberAfterInsert: newMember)
  112. }
  113. }
  114. return result
  115. }
  116. @discardableResult
  117. private func removeValue(forKey key: Identifier) -> ElementContainer? {
  118. var previousMember: ElementContainer?
  119. elements.mutate { (storage) in
  120. previousMember = storage.removeValue(forKey: key)
  121. }
  122. return previousMember
  123. }
  124. }