StorageObservableTask.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // Copyright 2022 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. #if COCOAPODS
  16. import GTMSessionFetcher
  17. #else
  18. import GTMSessionFetcherCore
  19. #endif
  20. /**
  21. * An extended `StorageTask` providing observable semantics that can be used for responding to changes
  22. * in task state.
  23. *
  24. * Observers produce a `StorageHandle`, which is used to keep track of and remove specific
  25. * observers at a later date.
  26. */
  27. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  28. @objc(FIRStorageObservableTask) open class StorageObservableTask: StorageTask {
  29. /**
  30. * Observes changes in the upload status: Resume, Pause, Progress, Success, and Failure.
  31. * - Parameters:
  32. * - status: The `StorageTaskStatus` change to observe.
  33. * - handler: A callback that fires every time the status event occurs,
  34. * containing a `StorageTaskSnapshot` describing task state.
  35. * - Returns: A task handle that can be used to remove the observer at a later date.
  36. */
  37. @objc(observeStatus:handler:) @discardableResult
  38. open func observe(_ status: StorageTaskStatus,
  39. handler: @escaping (StorageTaskSnapshot) -> Void) -> String {
  40. let callback = handler
  41. // Note: self.snapshot is synchronized
  42. let snapshot = self.snapshot
  43. // TODO: use an increasing counter instead of a random UUID
  44. let uuidString = updateHandlerDictionary(for: status, with: callback)
  45. if let handlerDictionary = handlerDictionaries[status] {
  46. switch status {
  47. case .pause:
  48. if state == .pausing || state == .paused {
  49. fire(handlers: handlerDictionary, snapshot: snapshot)
  50. }
  51. case .resume:
  52. if state == .resuming || state == .running {
  53. fire(handlers: handlerDictionary, snapshot: snapshot)
  54. }
  55. case .progress:
  56. if state == .running || state == .progress {
  57. fire(handlers: handlerDictionary, snapshot: snapshot)
  58. }
  59. case .success:
  60. if state == .success {
  61. fire(handlers: handlerDictionary, snapshot: snapshot)
  62. }
  63. case .failure:
  64. if state == .failed || state == .failing {
  65. fire(handlers: handlerDictionary, snapshot: snapshot)
  66. }
  67. case .unknown: fatalError("Invalid observer status requested, use one " +
  68. "of: Pause, Resume, Progress, Complete, or Failure")
  69. }
  70. }
  71. objc_sync_enter(StorageObservableTask.self)
  72. handleToStatusMap[uuidString] = status
  73. objc_sync_exit(StorageObservableTask.self)
  74. return uuidString
  75. }
  76. /**
  77. * Removes the single observer with the provided handle.
  78. * - Parameter handle: The handle of the task to remove.
  79. */
  80. @objc(removeObserverWithHandle:) open func removeObserver(withHandle handle: String) {
  81. if let status = handleToStatusMap[handle] {
  82. objc_sync_enter(StorageObservableTask.self)
  83. handlerDictionaries[status]?.removeValue(forKey: handle)
  84. handleToStatusMap.removeValue(forKey: handle)
  85. objc_sync_exit(StorageObservableTask.self)
  86. }
  87. }
  88. /**
  89. * Removes all observers for a single status.
  90. * - Parameter status: A `StorageTaskStatus` to remove all listeners for.
  91. */
  92. @objc(removeAllObserversForStatus:)
  93. open func removeAllObservers(for status: StorageTaskStatus) {
  94. if let handlerDictionary = handlerDictionaries[status] {
  95. objc_sync_enter(StorageObservableTask.self)
  96. for (key, _) in handlerDictionary {
  97. handleToStatusMap.removeValue(forKey: key)
  98. }
  99. handlerDictionaries[status]?.removeAll()
  100. objc_sync_exit(StorageObservableTask.self)
  101. }
  102. }
  103. /**
  104. * Removes all observers.
  105. */
  106. @objc open func removeAllObservers() {
  107. objc_sync_enter(StorageObservableTask.self)
  108. for (status, _) in handlerDictionaries {
  109. handlerDictionaries[status]?.removeAll()
  110. }
  111. handleToStatusMap.removeAll()
  112. objc_sync_exit(StorageObservableTask.self)
  113. }
  114. // MARK: - Private Handler Dictionaries
  115. var handlerDictionaries: [StorageTaskStatus: [String: (StorageTaskSnapshot) -> Void]]
  116. var handleToStatusMap: [String: StorageTaskStatus]
  117. /**
  118. * The file to download to or upload from
  119. */
  120. let fileURL: URL?
  121. // MARK: - Internal Implementations
  122. init(reference: StorageReference,
  123. service: GTMSessionFetcherService,
  124. queue: DispatchQueue,
  125. file: URL?) {
  126. handlerDictionaries = [
  127. .resume: [String: (StorageTaskSnapshot) -> Void](),
  128. .pause: [String: (StorageTaskSnapshot) -> Void](),
  129. .progress: [String: (StorageTaskSnapshot) -> Void](),
  130. .success: [String: (StorageTaskSnapshot) -> Void](),
  131. .failure: [String: (StorageTaskSnapshot) -> Void](),
  132. ]
  133. handleToStatusMap = [:]
  134. fileURL = file
  135. super.init(reference: reference, service: service, queue: queue)
  136. }
  137. func updateHandlerDictionary(for status: StorageTaskStatus,
  138. with handler: @escaping ((StorageTaskSnapshot) -> Void))
  139. -> String {
  140. // TODO: use an increasing counter instead of a random UUID
  141. let uuidString = NSUUID().uuidString
  142. objc_sync_enter(StorageObservableTask.self)
  143. handlerDictionaries[status]?[uuidString] = handler
  144. objc_sync_exit(StorageObservableTask.self)
  145. return uuidString
  146. }
  147. func fire(for status: StorageTaskStatus, snapshot: StorageTaskSnapshot) {
  148. if let observerDictionary = handlerDictionaries[status] {
  149. fire(handlers: observerDictionary, snapshot: snapshot)
  150. }
  151. }
  152. func fire(handlers: [String: (StorageTaskSnapshot) -> Void],
  153. snapshot: StorageTaskSnapshot) {
  154. let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
  155. objc_sync_enter(StorageObservableTask.self)
  156. let enumeration = handlers.enumerated()
  157. objc_sync_exit(StorageObservableTask.self)
  158. for (_, handler) in enumeration {
  159. callbackQueue.async {
  160. handler.value(snapshot)
  161. }
  162. }
  163. }
  164. }